年度归档:2019年

=====================================================
选择PyTorch的理由
容易上手的强大深度学习框架,像使用Python一样使用PyTorch
本书使用环境
Python3.6+,Pytorch1.0+,Tensorflow1.5+,GPU或CPU(无需变更代码)
视化工具
Matplotlib,TensorboardX等
2020年8月之后我们在后面补充了一些新内容
=====================================================

欢迎加入《Python深度学习基于PyTorch》的QQ交流群:在线答疑与交流!

=====================================================

本书代码及数据下载

第一部分 基础篇
第1章 NumPy基础
1.1 生成NumPy数组
1.1.1 从已有数据中创建数组
1.1.2 利用 random 模块生成数组
1.1.3 创建特定形状的多维数组
1.1.4 利用 arange、linspace 函数生成数组
1.2 获取元素
1.3 NumPy的算术运算
1.3.1对应元素相乘
1.3.2 点积运算
1.4 数组变形
1.4.1 更改数组的形状
1.4.2 合并数组
1.5 批量处理
1.6 通用函数
1.7 广播
1.8 小结
补充:Pandas基础篇

补充:Pandas提高篇
【pandas所需数据已放在”本书代码及数据“部分】

第2章 PyTorch基础
2.1 为何选择Pytorch?
2.2 安装配置
2.2.1 CPU版Pytorch
2.2.2 GPU版Pytorch
2.3 Jupyter Notebook环境配置
2.4 Numpy与Tensor
2.4.1 Tensor概述
2.4.2 创建Tensor
2.4.3 修改Tensor形状
2.4.4 索引操作
2.4.5 广播机制
2.4.6 逐元素操作
2.4.7 归并操作
2.4.8 比较操作
2.4.9 矩阵操作
2.4.10 Pytorch与Numpy比较
2.5 Tensor与Autograd
2.5.1 自动求导要点
2.5.2计算图
2.5.3 标量反向传播
2.5.4 非标量反向传播
2.6 使用Numpy实现机器学习
2.7 使用Tensor及antograd实现机器学习
2.8 使用TensorFlow架构
2.9 小结
第3章 Pytorch实现神经网络工具箱
3.1 神经网络核心组件
3.2实现神经网络实例
3.2.1背景说明
3.2.2准备数据
3.2.3可视化源数据
3.2.4 构建模型
3.2.5 训练模型
3.3 如何构建神经网络?
3.3.1 构建网络层
3.3.2 前向传播
3.3.3 反向传播
3.3.4 训练模型
3.4 nn.Module
3.5 nn.functional
3.6 优化器
3.7 动态修改学习率参数
3.8 优化器比较
3.9 小结
第4章 Pytorch数据处理工具箱
4.1 数据处理工具箱概述
4.2 utils.data简介
4.3 torchvision简介
4.3.1 transforms
4.3.2 ImageFolder
4.4 可视化工具
4.4.1 tensorboardX简介
4.4.2用tensorboardX可视化神经网络
4.4.3用tensorboardX可视化损失值
4.4.4用tensorboardX可视化特征图
4.5 小结
第二部分 深度学习基础
第5 章 机器学习基础
5.1 机器学习的基本任务
5.1.1监督学习
5.1.2 无监督学习
5.1.3 半监督学习
5.1.4 强化学习
5.2 机器学习一般流程
5.2.1 明确目标
5.2.2收集数据
5.2.3 数据探索与预处理
5.2.4 选择模型
5.2.5 评估及优化模型
5.3 过拟合与欠拟合
5.3.1 权重正则化
5.3.2 dropout正则化
5.3.3 批量正则化
5.3.4权重初始化
5.4 选择合适激活函数
5.5 选择合适的损失函数
5.6 选择合适优化器
5.6.1传统梯度优化的不足
5.6.2动量算法
5.6.3 AdaGrad算法
5.6.4 RMSProp算法
5.6.5 Adam算法
5.7GPU加速
5.7.1 单GPU加速
5.7.2 多GPU加速
5.7.3使用GPU注意事项
5.8 小结
第6章 视觉处理基础
6.1卷积神经网络简介
6.2卷积层
6.2.1 卷积核
6.2.2步幅
6.2.3 填充
6.2.4 多通道上的卷积
6.2.5激活函数
6.2.6卷积函数
6.2.7转置卷积
6.3池化层
6.3.1局部池化
6.3.2全局池化
6.4现代经典网络
6.4.1 LeNet-5模型
6.4.2 AlexNet模型
6.4.3 VGG模型
6.4.4 GoogleNet模型
6.4.5 ResNet模型
6.4.6 胶囊网络简介
6.5 Pytorch实现cifar10多分类
6.5.1 数据集说明
6.5.2 加载数据
6.5.3 构建网络
6.5.4 训练模型
6.5.5 测试模型
6.5.6 采用全局平均池化
6.5.7像keras一样显示各层参数
6.6 模型集成提升性能
6.6.1 使用模型
6.6.2 集成方法
6.6.3 集成效果
6.7使用经典模型提升性能
6.8 小结
第6章补充 如何用PyTorch加载自己的数据集?

第7章 自然语言处理基础
7.1 循环神经网络基本结构
7.2前向传播与随时间反向传播
7.3 循环神经网络变种
7.3.1 LSTM
7.3.2 GRU
7.3.3 Bi-RNN
7.4 循环神经网络的Pytorch实现
7.4.1 RNN实现
7.4.2LSTM实现
7.4.3GRU实现
7.5文本数据处理
7.6词嵌入
7.6.1Word2Vec原理
7.6.2 CBOW模型
7.6.3 Skim-gram模型
7.7 Pytorch实现词性判别
7.7.1 词性判别主要步骤
7.7.2 数据预处理
7.7.3 构建网络
7.7.4 训练网络
7.7.5 测试模型
7.8循环神经网络应用场景
7.9 小结
第8章 生成式深度学习
8.1 用变分自编码器生成图像
8.1.1 自编码器
8.1.2变分自编码器
8.1.3用变分自编码器生成图像
8.2 GAN简介
8.2.1 GAN架构
8.2.2 GAN的损失函数
8.3用GAN生成图像
8.3.1判别器
8.3.2 生成器
8.3.3 训练模型
8.3.4 可视化结果
8.4 VAE与GAN的异同
8.5 Condition GAN
8.5.1 CGAN的架构
8.5.2 CGAN 生成器
8.5.3 CGAN 判别器
8.5.4 CGAN 损失函数
8.5.5 CGAN 可视化
8.5.6 查看指定标签的数据
8.5.7 可视化损失值
8.6 DCGAN
8.7 提升GAN训练效果的一些技巧
8.8 小结
第三部分 深度学习实战
第9章 人脸检测与识别
9.1 人脸识别一般流程
9.1.1图像采集
9.1.2 人脸检测
9.3特征提取
9.4人脸识别
9.4.1 人脸识别主要原理
9.4.2人脸识别发展
9.5 人脸检测与识别实例
9.5.1.验证检测代码
9.5.2.检测图像
9.5.3.检测后进行预处理
9.5.4.查看经检测后的图片
9.5.5.人脸识别
9.6 小结
第10章 迁移学习实例
10.1 迁移学习简介
10.2 特征提取
10.2.1 Pytorch提供的预处理模块
10.2.2 特征提取实例
10.3 数据增强
10.3.1 按比例缩放
10.3.2 裁剪
10.3.3翻转
10.3.4改变颜色
10.3.5组合多种增强方法
10.4 微调实例
10.4.1 数据预处理
10.4.2 加载预训练模型
10.4.3 修改分类器
10.4.4 选择损失函数及优化器
10.4.5 训练及验证模型
10.5 用预训练模型清除图像中的雾霾
10.5.1 导入需要的模块
10.5.2 查看原来的图像
10.5.3 定义一个神经网络
10.5.4 训练模型
10.5.5 查看处理后的图像
10.6 小结
第11章 神经网络机器翻译实例
11.1 Encode-Decoder模型原理
11.2 注意力框架
11.3 Pytorch实现注意力Decoder
11.3.1 构建Encoder
11.3.2 构建简单Decoder
11.3.3 构建注意力Decoder
11.4 用注意力机制实现中英文互译
11.4.1 导入需要的模块
11.4.2数据预处理
11.4.3构建模型
11.4.4训练模型
11.4.5随机采样,对模型进行测试
11.4.6可视化注意力
11.5 小结
第12章 实战生成式模型
12.1 Deep Dream模型
12.1.1 Deep Dream原理
12.1.2 DeepDream算法流程
12.1.3 用Pytorch实现Deep Dream
12.2 风格迁移
12.2.1 内容损失
12.2.2 风格损失
12.2.3 用Pytorch实现神经网络风格迁移
12.3 Pytorch实现图像修复
12.3.1 网络结构
12.3.2 损失函数
12.3.3 图像修复实例
12.4 Pytorch实现DiscoGAN
12.4.1 DiscoGAN架构
12.4.2 损失函数
12.4.3 DiscoGAN实现
12.4.4 用Pytorch实现从边框生成鞋子
第13章 Caffe2模型迁移实例
13.1 Caffe2简介
13.2 Caffe如何迁移到Caffe2
13.3 Pytorch如何迁移到caffe2
13.4 小结
第14章 AI新方向:对抗攻击
14.1对抗攻击简介
14.1.1白盒攻击与黑盒攻击
14.1.2无目标攻击与有目标攻击
14.2常见对抗样本生成方式
14.2.1快速梯度符号法
14.2.2快速梯度算法
14.3 Pytorch实现对抗攻击
14.3.1 实现无目标攻击
14.3.2 实现有目标攻击
14.4 对抗攻击和防御措施
14.4.1 对抗攻击
14.4.2 常见防御方法分类
14.5 总结
第15章 强化学习
15.1 强化学习简介
15.2Q Learning 原理
15.2.1 Q Learning主要流程
15.2.2 Q函数
15.2.3 贪婪策略
15.3 用Pytorch实现Q Learning
15.3.1 定义Q-Learing主函数
15.3.2执行Q-Learing
15.4 SARSA 算法
15.4.1 SARSA算法主要步骤
15.4.2 用Pytorch实现SARSA算法
15.5 小结
第16章 深度强化学习
16.1 DSN算法原理
16.1.1 Q-Learning方法的局限性
16.1.2 用DL处理RL需要解决的问题
16.1.3 用DQN解决方法
16.1.4 定义损失函数
16.1.5 DQN的经验回放机制
16.1.6 目标网络
16.1.7 网络模型
16.1.8 DQN算法
16.2 用Pytorch实现 DQN算法
16.3 小结
附录A:Pytorch0.4版本变更
A.1概述
A.2 合并Variable和Tensor
A.3 弃用volatile标签
A.4 dypes,devices以及numpy-style的构造函数
A.5 迁移实例比较
附录B:AI在各行业的最新应用
B.1 AI+电商
B.2 AI+金融
B.3 AI+医疗
B.4 AI+零售
B.5 AI+投行
B.6 AI+制造
B.7 AI+IT服务
B.8 AI+汽车
B.9 AI+公共安全

本书补充材料

一、用PyTorch如何加载自己的数据集?
二、全面、透彻掌握序列建模

这节我们介绍利用一个预训练模型清除图像中雾霾,使图像更清晰。

26.1 导入需要的模块

26.2 查看原来的图像

26.3 定义一个神经网络

这个神经网络主要由卷积层构成,该网络将构建在预训练模型之上。

26.4 训练模型

clean_photo/test_images/shanghai02.jpg done!

26.5 查看处理后的图像

处理后的图像与原图像拼接在一起,保存在clean_photo /results目录下。

虽非十分理想,但效果还是比较明显的!
本章数据集下载地址(提取码是:1nxs)
更多内容可参考:
https://github.com/TheFairBear/PyTorch-Image-Dehazing

深度学习涉及很多向量或多矩阵运算,如矩阵相乘、矩阵相加、矩阵-向量乘法等。深层模型的算法,如BP,Auto-Encoder,CNN等,都可以写成矩阵运算的形式,无须写成循环运算。然而,在单核CPU上执行时,矩阵运算会被展开成循环的形式,本质上还是串行执行。GPU(Graphic Process Units,图形处理器)的众核体系结构包含几千个流处理器,可将矩阵运算并行化执行,大幅缩短计算时间。随着NVIDIA、AMD等公司不断推进其GPU的大规模并行架构,面向通用计算的GPU已成为加速可并行应用程序的重要手段。得益于GPU众核(many-core)体系结构,程序在GPU系统上的运行速度相较于单核CPU往往提升几十倍乃至上千倍。
目前,GPU已经发展到了较为成熟的阶段。利用GPU来训练深度神经网络,可以充分发挥其数以千计计算核心的能力,在使用海量训练数据的场景下,所耗费的时间大幅缩短,占用的服务器也更少。如果对适当的深度神经网络进行合理优化,一块GPU卡相当于数十甚至上百台CPU服务器的计算能力,因此GPU已经成为业界在深度学习模型训练方面的首选解决方案。
如何使用GPU?现在很多深度学习工具都支持GPU运算,使用时只要简单配置即可。Pytorch支持GPU,可以通过to(device)函数来将数据从内存中转移到GPU显存,如果有多个GPU还可以定位到哪个或哪些GPU。Pytorch一般把GPU作用于张量(Tensor)或模型(包括torch.nn下面的一些网络模型以及自己创建的模型)等数据结构上。

25.1 单GPU加速

使用GPU之前,需要确保GPU是可以使用,可通过torch.cuda.is_available()的返回值来进行判断。返回True则具有能够使用的GPU。
通过torch.cuda.device_count()可以获得能够使用的GPU数量。
如何查看平台GPU的配置信息?在命令行输入命令nvidia-smi即可 (适合于Linux或Windows环境)。图5-13是GPU配置信息样例,从中可以看出共有2个GPU。

图5-13 GPU配置信息

把数据从内存转移到GPU,一般针对张量(我们需要的数据)和模型。
对张量(类型为FloatTensor或者是LongTensor等),一律直接使用方法.to(device)或.cuda()即可。

对于模型来说,也是同样的方式,使用.to(device)或.cuda来将网络放到GPU显存。

25.2 多GPU加速

这里我们介绍单主机多GPUs的情况,单机多GPUs主要采用的DataParallel函数,而不是DistributedParallel,后者一般用于多主机多GPUs,当然也可用于单机多GPU。
使用多卡训练的方式有很多,当然前提是我们的设备中存在两个及以上的GPU。
使用时直接用model传入torch.nn.DataParallel函数即可,如下代码:

这时,默认所有存在的显卡都会被使用。
如果你的电脑有很多显卡,但只想利用其中一部分,如只使用编号为0、1、3、4的四个GPU,那么可以采用以下方式:

或者

其中CUDA_VISIBLE_DEVICES 表示当前可以被Pytorch程序检测到的GPU。
下面为单机多GPU的实现代码。
(1)背景说明
这里使用波士顿房价数据为例,共506个样本,13个特征。数据划分成训练集和测试集,然后用data.DataLoader转换为可批加载的方式。采用nn.DataParallel并发机制,环境有2个GPU。当然,数据量很小,按理不宜用nn.DataParallel,这里只是为了说明使用方法。
(2)加载数据

(2)把数据转换为批处理加载方式
批次大小为128,打乱数据。

(3)定义网络

(4)把模型转换为多GPU并发处理格式

运行结果
Let's use 2 GPUs
DataParallel(
(module): Net1(
(layer1): Sequential(
(0): Linear(in_features=13, out_features=16, bias=True)
)
(layer2): Sequential(
(0): Linear(in_features=16, out_features=32, bias=True)
)
(layer3): Sequential(
(0): Linear(in_features=32, out_features=1, bias=True)
)
)
)
(5)选择优化器及损失函数

(6)模型训练,并可视化损失值。

运行的部分结果
In Model: input size torch.Size([64, 13]) output size torch.Size([64, 1])
In Model: input size torch.Size([64, 13]) output size torch.Size([64, 1])
Outside: input size torch.Size([128, 13]) output_size torch.Size([128, 1])
In Model: input size torch.Size([64, 13]) output size torch.Size([64, 1])
In Model: input size torch.Size([64, 13]) output size torch.Size([64, 1])
Outside: input size torch.Size([128, 13]) output_size torch.Size([128, 1])
从运行结果可以看出,一个批次数据(batch-size=128)拆分成两份,每份大小为64,分别放在不同的GPU上。此时用GPU监控也可发现,两个GPU都同时在使用。

(7)通过web查看损失值的变化情况

图5-16 并发运行训练损失值变化情况
图形中出现较大振幅,是由于采用批次处理,而且数据没有做任何预处理,对数据进行规范化应该更平滑一些,大家可以尝试一下。

单机多GPU也可使用DistributedParallel,它多用于分布式训练,但也可以用在单机多GPU的训练,配置比使用nn.DataParallel稍微麻烦一点,但是训练速度和效果更好一点。具体配置为:

单机运行时使用下面方法启动

25.3使用GPU注意事项

使用GPU可以提升我们训练的速度,如果使用不当,可能影响使用效率,具体使用时要注意以下几点:
(1)GPU的数量尽量为偶数,奇数的GPU有可能会出现异常中断的情况;
(2)GPU很快,但数据量较小时,效果可能没有单GPU好,甚至还不如CPU;
(3)如果内存不够大,使用多GPU训练的时候可通过设置pin_memory为False,当然使用精度稍微低一点的数据类型有时也效果。

24.1 链式法则

假设z=f(t)
t=g(y)
则,根据复合函数的求导规则(即链式法则),可得:

24.1.1 计算图的方向传播

反向传播的计算顺序是将信号E乘以节点的局部导数(∂y/∂x),然后将结果传递给下一个节点。

24.1.2 链式法则

如果某个函数为复合函数,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。

24.1.3 链式法则和计算图

反向传播是上游传过来的导数乘以本节点导数。

24.2 反向传播

反向传播是上游传过来的导数乘以本节点对应输入的导数。

24.2.1 加法节点的反向传播

 

代码实现如下

24.2.2 乘法法节点的反向传播

24.2.3 激活函数层的反向传播

24.3 激活函数层的反向传播

24.3.1 ReLU激活函数

24.3.2 Sigmoid激活函数


根据导数的链式规则,上游的值乘以本节点输出对输入的导数.
代码实现

24.4 Affine/softmax层的反向传播


DY=np.array([[1,2,3],[4,5,6]])
dB=np.sum(DY,axis=0)
Affine层的代码实现

24.4.1 Softmax-with loss 层

输入一张手写5的图片,经过多层(这里假设为2层)神经网络转换后,对输出10个节点,在各个输出节点的得分或概率是不同的,其中对应标签为5的节点(转换为one
-hot后为[0,0,0,0,1,0,0,0,0],得分或概率最大。

我们看一下带softmax及loss的反向传播,如何计算梯度。以下为示意图。

代码实现

24.5 损失反向传播法的实现

24.5.1 神经网络学习的基本步骤

利用随机梯度下降法,求梯度并更新权重和偏置参数,整个过程是个循环过程。
步骤1
从训练数据中随机选择一部分数据
步骤2
构建网络,利用前向传播,求出输出值。然后利用输出值与目标值得到损失函数,利用损失函数,利用反向传播方法,求各参数的梯度。
步骤3
将权重参数沿梯度方向进行微小更新
步骤4
重复以上1、2、3步骤

24.5.2 神经网络学习的反向传播法的实现

神经网络结构图如下

下面用代码实现
1)概述
为定义和保存以上神经网络架构,需要先定义几个实例变量:
保存权重参数的字典型变量params。
保存各层的信息的顺序字典layers,这里的顺序是插入数据的先后顺序。
神经网络的最后一层lastlayer
除了以上三个实例变量,还需要定义一些方法
构造函数,以初始化变量和权重等
预测方法,根据神经网络各层的前向传播得到最后的输出值
损失函数,根据输出值与目标值,得到交叉熵作为衡量两个分布的距离。
评估指标,这里使用精度来衡量模型性能
最后就是计算梯度,这里使用反向传播方法的得到,具体是利用导数的链式法则,从后往前,获取各层的梯度作为前层梯度往前传递(往输入端)
当然,这里需要先定义好各层类,各类中各层的权重参数、包括前向传播结果,反向传播的梯度等。
2)定义各层类
①softmax 函数及Sigmoid类

②Affine类或称为sumweigt

③最后一层

3)定义损失函数

4)定义神经网络类

5)使用误差反向传播法训练模型

.09000000000000001
train acc, test acc | 0.9837833333333333, 0.9724
0.09000000000000001
train acc, test acc | 0.9842666666666666, 0.9722
0.08100000000000002
train acc, test acc | 0.98475, 0.9716
0.08100000000000002
train acc, test acc | 0.9853166666666666, 0.9733
0.08100000000000002
train acc, test acc | 0.9859666666666667, 0.9726
0.08100000000000002
train acc, test acc | 0.9861166666666666, 0.9707
0.08100000000000002
train acc, test acc | 0.9873, 0.9737
0.08100000000000002
train acc, test acc | 0.9873833333333333, 0.9744
0.08100000000000002
train acc, test acc | 0.9881, 0.973
0.08100000000000002
train acc, test acc | 0.9886666666666667, 0.9747
0.08100000000000002
train acc, test acc | 0.9888833333333333, 0.9743

6)利用各种算法对MNIST数据集的影响

本章数据集下载地址(提取码是:7kct)

神经风格迁移是指将参考图像的风格应用于目标图像,同时保留目标图形的内容,如下图所示:

实现风格迁移核心思想就是定义损失函数,然后最小化损失。这里的损失包括风格损失和内容损失。
用公式来表示就是:

具体内容如下图

如图,假设初始化图像x(Input image)是一张随机图片,我们经过fw(image Transform Net)网络进行生成,生成图片y。
此时y需要和风格图片ys进行特征的计算得到一个loss_style,与内容图片yc进行特征的计算得到一个loss_content,假设loss=loss_style+loss_content,便可以对fw的网络参数进行训练。

23.1 内容损失

内容损失一般选择靠近的某层激活的差平方或L2范数。
写成代码就是

23.2 风格损失

格拉姆矩阵(Gram Matrix),即某一层特征图的内积。这个内积可以理解为表示该层特征之间相互关系的映射。损失函数的定义主要考虑以下因素:
①在目标内容图像和生成图像之间保持相似的较高层激活,从而能保留内容。卷积神经网络应该能够看到目标图像和生成图像包含相同的内容。
②在较低层和较高层的激活中保持类似的相互关系,从而能保留风格。特征相互关系捕zu到的是纹理,生成图像和风格参考图像在不同的空间尺度上应该具有相同纹理。

Gram Matrices的计算过程
假设输入图像经过卷积后,得到的feature map为[ch, h, w]。我们经过flatten和矩阵转置操作,可以变形为[ ch, h*w]和[h*w, ch]的矩阵。再对两矩阵做内积得到[ch, ch]大小的矩阵,这就是我们所说的Gram Matrices,如下图所示:

 

比如我们假设输入图像经过卷积后得到的[b, ch, h*w]的feature map,其中我们用fm表示第m个通道的特征层,fn为第n通道特征层。则Gram Matrices中元素fm∗fn代表的就是m通道和n通道特征flatten后按位相乘(内积)
具体实现代码

关于Gram矩阵还有以下三点值得注意:
1 Gram矩阵的计算采用了累加的形式,抛弃了空间信息。一张图片的像素随机打乱之后计算得到的Gram Matrix和原图的Gram Matrix一样。所以认为Gram Matrix所以认为Gram Matrix抛弃了元素之间的空间信息。
2 Gram Matrix的结果与feature maps F 的尺寸无关,只与通道个数有关,无论H,W的大小如何,最后Gram Matrix的形状都是CxC
3 对于一个C x H x W的feature maps,可以通过调整形状和矩阵乘法运算快速计算它的Gram Matrix。即先将F调整到 C x (H x W)的二维矩阵,然后再计算F 和F的转置。结果就为Gram Matrix

Gram Matrix的特点:
通过相乘运算,它将特征之间的区别进行扩大或者缩小,由此可一定程度反应向量本身及向量之间的一些特征或关系,它注重风格纹理,忽略空间信息

23.3 用keras实现神经风格迁移

https://ypw.io/style-transfer/(神经风格迁移 pytorch 0.4)
1)导入目标、风格图像

2)定义图像处理辅助函数
对进出VGG19神经网络的图像进行加载、预处理和后处理等处理。

【说明】
keras中preprocess_input()函数的作用是对样本执行 逐样本均值消减 的归一化,即在每个维度上减去样本的均值,对于维度顺序是channels_last的数据,keras中每个维度上的操作如下:

3)加载VGG19网络,并将其应用于三张图像
三张图像是目标图像、风格图像、生成图像,把这三张图像作为一个批量。其中生成图像将改变,以占位符的形式存储。而目标图像、风格图像在整个过程中是不变的,故以constant方式存储。

Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80142336/80134624 [==============================] - 155s 2us/step
Model loaded.

4)查看VGG19的网络结构图

_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, None, None, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, None, None, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, None, None, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, None, None, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, None, None, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, None, None, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, None, None, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, None, None, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, None, None, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, None, None, 256) 590080
_________________________________________________________________
block3_conv4 (Conv2D) (None, None, None, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, None, None, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, None, None, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block4_conv4 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, None, None, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block5_conv4 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, None, None, 512) 0
=================================================================
Total params: 20,024,384
Trainable params: 20,024,384
Non-trainable params: 0
_________________________________________________________________
None

VGG19网络的结构图

5)定义内容损失

内容损失最小化,以保证目标图像和生成图像在VGG19卷积神经网络的顶层(即block5-conv2)具有相似结果。

6)定义风格损失函数
使用一个辅助函数来计算输入矩阵的格拉姆矩阵,即原始特征矩阵中相互关系的映射。

假设输入图像经过卷积后,得到的feature map为[ch, h, w]。我们经过flatten和矩阵转置操作,可以变形为[ ch, h*w]和[h*w, ch]的矩阵。再对两矩阵做内积得到[ch, ch]大小的矩阵,这就是我们所说的Gram Matrices,如下图所示:

7)定义总变差损失函数
除了以上两个损失函数,还需要一个总变差损失,它对生成的图像的像素进行正则化等操作,它促使生成图像具有空间的连续性,以避免结果过度像素化。

8)定义总损失函数
总损失是内容损失、风格损失、总变差损失的加权损失。网络顶层包含更加全局、更加抽象的信息,所以内容损失只使用一个顶层,即block5_conv2层;每层对都有不同风格,所以对风格损失需要使用一系列的层(block1_conv1、block2_conv1、block3_conv1、block4_conv1、block5_conv1)

9)L_BFGS算法简介
这里使用scipy中L_BFGS算法进行最优化。为便于大家理解该优化器,这里我们先简单介绍一下L_BFGS算法对应的函数格式及示例。
fmin_l_bfgs_b函数格式:

使用示例:

10)定义生成图像的优化器
通过优化器获取梯度、损失等

11)训练模型

12)可视化目标图、参考风格图、生成图等。

 

人们常说,神经网络模型就像一个“黑盒”,这对一些神经网络模型确实如此,不过卷积神经网络,在可视化方面取得长足进步,我们可以看到卷积的中间结果、可视化不同的卷积核、可视化图像中类激活的热力图(决定类分类的关键区域)。
这三种方法的具体内容为:
1、卷积核输出的可视化(Visualizing intermediate convnet outputs (intermediate activations),即可视化卷积核经过激活之后的结果。能够看到图像经过卷积之后结果,帮助理解卷积核的作用
2、卷积核的可视化(Visualizing convnets filters),帮助我们理解卷积核是如何感受图像的。
3、热度图可视化(Visualizing heatmaps of class activation in an image),通过热度图,了解图像分类问题中图像哪些部分起到了关键作用,同时可以定位图像中物体的位置。

22.1 可视化中间结果

可视化中间结果,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常是激活函数的输出,故又称为该层的激活)。
每个通道上特征图是相对独立的,我们可以将这些特征图可视化的正确方法是将每个通道的内容分别绘制成二维图像。
1)可视化下例模型的中间输出
本章模型cats_and_dogs_small_2.h5、图像cat.1700.jpg、creative_commons_elephant.jpg
下载地址

运行结果
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_13 (Conv2D) (None, 148, 148, 32) 896
_________________________________________________________________
max_pooling2d_13 (MaxPooling (None, 74, 74, 32) 0
_________________________________________________________________
conv2d_14 (Conv2D) (None, 72, 72, 64) 18496
_________________________________________________________________
max_pooling2d_14 (MaxPooling (None, 36, 36, 64) 0
_________________________________________________________________
conv2d_15 (Conv2D) (None, 34, 34, 128) 73856
_________________________________________________________________
max_pooling2d_15 (MaxPooling (None, 17, 17, 128) 0
_________________________________________________________________
conv2d_16 (Conv2D) (None, 15, 15, 128) 147584
_________________________________________________________________
max_pooling2d_16 (MaxPooling (None, 7, 7, 128) 0
_________________________________________________________________
flatten_5 (Flatten) (None, 6272) 0
_________________________________________________________________
dropout_5 (Dropout) (None, 6272) 0
_________________________________________________________________
dense_13 (Dense) (None, 512) 3211776
_________________________________________________________________
dense_14 (Dense) (None, 1) 513
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0

2)获取测试数据中的一张图像

运行结果
(1, 150, 150, 3)
转换为3D

(150, 150, 3)

3)可视化这个图像

4)抽取前8层的特征图或激活输出

5) 返回8个Numpy数组组成的列表,每个激活输出对应一个Numpy数组

(1, 148, 148, 32)
6) 查看第一层,第4个通道的激活输出图像

第4通道似乎是对角边缘检测器。
查看第7个通道的输出图像

第7通道似乎是圆点检测器,这对寻找猫眼睛非常有帮助。

7)把各通道组合成一个完整图形

上图从第1层到第8层,各通道的拼接图,从这些拼接图可以看出:
①第一层是各种边缘探测器的集合。在这一阶段,激活几乎保留了原始图像中的所有信息。
②随着层数的加深,激活变得越来越抽象,并且越来越难以直观理解。层数越深,关于图像视觉内容的信息越少,而关于类别的信息就越多。
③激活的稀疏性随着层数的加深而增大。

22.2 可视化卷积网络的过滤器

参考:https://blog.csdn.net/weiwei9363/article/details/79112872
https://www.jianshu.com/p/fb3add126da1
卷积核到底是如何识别物体的呢?想要解决这个问题,有一个方法就是去了解卷积核最感兴趣的图像是怎样的。我们知道,卷积的过程就是特征提取的过程,每一个卷积核代表着一种特征。如果图像中某块区域与某个卷积核的结果越大,那么该区域就越“像”该卷积核。
基于以上的推论,如果我们找到一张图像,能够使得这张图像对某个卷积核的输出最大,那么我们就说找到了该卷积核最感兴趣的图像。
具体思路:输入一张随机内容的图像I, 求某个卷积核F对图像的梯度 G=∂F/∂I,用梯度上升的方法迭代更新图像 I=I+η∗G,η为学习率。
我们可以从空白输入图像开始,将梯度下降应用于卷积神经网络输入图像的值,让某个过滤器的响应最大化。这样得到的输入图像就是选定过滤器具有最大响应的图形。
具体过程,
1)先构建一个损失函数,让某个卷积层的某个过滤器作用输入图像的激活值最大化;
2)使用随机梯度下降来调节输入图像的值,以便让这个激活值最大化。
以下我们以VGG16网络的block3_conv1层的第0个过滤器为例,
(1)首先构建有关过滤器激活值的损失函数。

(2)为了求相对于模型输入loss的梯度,可以使用keras的backend模块内置的gradients函数。

为便于计算梯度,使梯度用L2进行标准化处理

(3)利用keras后端函数计算loss及梯度
利用keras后端函数,可以根据一个输入图像,计算损失张量和梯度张量的值。

利用一个循环进行随机梯度下降,从而更新梯度

为便于可视化,对输入图像进行预处理。

(4)将以上代码整合到一个函数中

(5)可视化每一层前64个卷积核

block1_conv1 层前64个过滤器模式

block2_conv1 前64个过滤器模式

block3-conv1 前64过滤器模式

block4-conv1 前64个过滤器模式
结论:
低层的卷积核似乎对颜色,边缘信息感兴趣。
越高层的卷积核,感兴趣的内容越抽象(非常魔幻啊),也越复杂。
高层的卷积核感兴趣的图像越来越难通过梯度上升获得(block5_conv1有很多还是随机噪声的图像)

22.3 可视化类激活的热力图

1)CAM
在介绍Grad-CAM,Grad- Class Activation Mapping)之前,我们先介绍一下CAM。
我们日常生活中讲的热力图,是根据动物散发热量而形成的图形,图1中动物或人因为散发出热量,所以能够清楚的被看到。

图1
这次我们讲的深度学习中的类激活的热力图与此类似。
对一个深层的卷积神经网络而言,通过多次卷积和池化以后,它的最后一层卷积层包含了最丰富的空间和语义信息,再往下就是全连接层和softmax层了,如图2,其中所包含的信息都是难以理解的,难以可视化的方式展示出来。如果要让卷积神经网络的对其分类结果给出一个合理解释,充分利用好最后一个卷积层是关键。

图2
CAM借鉴了很著名的论文Network in Network中的思路,利用GAP(Global Average Pooling)替换掉了全连接层。可以把GAP视为一个特殊的average pool层,只不过其pool size和整个特征图一样大,其实就是求每张特征图所有像素的均值。具体可参考图3

图3

图4
GAP(参考图4)的优点在NIN的论文中说的很明确了:由于没有了全连接层,输入就不用固定大小了,因此可支持任意大小的输入;此外,引入GAP更充分的利用了空间信息,且没有了全连接层的各种参数,鲁棒性强,也不容易产生过拟合;还有很重要的一点是,在最后的 mlpconv层(也就是最后一层卷积层)强制生成了和目标类别数量一致的特征图,经过GAP以后再通过softmax层得到结果,这样做就给每个特征图赋予了很明确的意义。
我们重点看下经过GAP之后与输出层的连接关系(暂不考虑softmax层),实质上也是就是个全连接层,只不过没有了偏置项,如图4所示:

图5
从图5中可以看到,经过GAP之后,我们得到了最后一个卷积层每个特征图的均值,通过加权和得到输出(实际中是softmax层的输入)。需要注意的是,对每一个类别C,每个特征图k的均值都有一个对应的w,记为ω_k^c。CAM的基本结构就是这样了,下面就是和普通的CNN模型一样训练就可以了。训练完成后才是重头戏:我们如何得到一个用于解释分类结果的热力图呢?其实非常简单,比如说我们要解释为什么分类的结果是羊驼,我们把羊驼这个类别对应的所有ω_k^c取出来,求出它们与自己对应的特征图的加权和即可。由于这个结果的大小和特征图是一致的,我们需要对它进行上采样,叠加到原图上去,如图6所示。

图6
这样,CAM以热力图的形式告诉了我们,模型通过哪些像素确定这个图片是羊驼了
2)Grad-CAM
前面我们简单介绍了CAM,CAM的解释效果已经很不错了,但是它有一个不足,就是它要求修改原模型的结构,导致需要重新训练该模型,这大大限制了它的使用场景。如果模型已经上线了,或着训练的成本非常高,我们几乎是不可能为了它重新训练的。为了解决这个问题,人们就提出了Grad-CAM。
Grad-CAM的基本思路和CAM是一致的,也是通过得到每对特征图对应的权重,最后求一个加权和。但是它与CAM的主要区别在于求权重ω_k^c的过程。CAM通过替换全连接层为GAP层,重新训练得到权重,而Grad-CAM另辟蹊径,用梯度的全局平均来计算权重。事实上,经过严格的数学推导,Grad-CAM与CAM计算出来的权重是等价的。为了和CAM的权重做区分,定义Grad-CAM中第k个特征图对类别c的权重为ω_k^c,可通过下面的公式计算:

其中,Z为特征图的像素个数,y^c是对应类别c的分数(在代码中一般用logits表示,是输入softmax层之前的值),A_ij^k表示第k个特征图中,(i,j)位置处的像素值。求得类别对所有特征图的权重后,求其加权和就可以得到热力图。

Grad-CAM的整体结构如下图所示:

图7
注意这里和CAM的另一个区别是,Grad-CAM对最终的加权和加了一个ReLU,加这么一层ReLU的原因在于我们只关心对类别c有正影响的那些像素点,如果不加ReLU层,最终可能会带入一些属于其它类别的像素,从而影响解释的效果。使用Grad-CAM对分类结果进行解释的效果如下图所示:

图8
3)用Keras如何实现Grad-CAM?
①加载带有预训练权重的VGG16网络

Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg16_weights_tf_dim_ordering_tf_kernels.h5
553467904/553467096 [==============================] - 2601s 5us/step
②预处理一张图片

查看预训练的VGG16网络,并将其预测向量解码为人们可读的格式。

redicted: [('n02504458', 'African_elephant', 0.90942115), ('n01871265', 'tusker', 0.08618273), ('n02504013', 'Indian_elephant', 0.004354583)]

从上面运行结果可以看出:
非洲象(African_elephant),占90%
长牙动物(tusker),占8%
印度象(Indian_elephant),占0.4%
网络识别出图像中包含数据量不确定的非洲象。预测向量中被最大激活的元素是对应“非洲象”类别元素(即类别概率最大项),索引编号为386

386
④ 实现Grad-CAM算法

⑥可视化类激活图

图9
⑦把原始图叠加在刚生成的热力图上

图10

参考资料:
《Python深度学习》弗朗索瓦•肖莱著
https://bindog.github.io/blog/2018/02/10/model-explanation/
http://spytensor.com/index.php/archives/20/(包括keras、pytorch实现Grad-CAM算法,class activation map)
http://spytensor.com/index.php/archives/19/(介绍GAP)