第4章 Pytorch数据处理工具箱


在3.2节我们利用Pytorch的torchvision、data等包,下载及预处理MNIST数据集。数据下载和预处理是机器学习、深度学习实际项目中耗时又重要的任务,尤其是数据预处理,关系到数据质量和模型性能,往往要占据项目的大部分时间。好在Pytorch为此提供了专门的数据下载、数据处理包,使用这些包,可极大提高我们的开发效率及数据质量。
本章将介绍以下内容:
 简单介绍Pytorch相关的数据处理工具箱
 utils.data简介
 torchvision简介
 tensorboardX简介及实例

4.1 数据处理工具箱概述

如果你看了第3章,应该对torchvision、data等数据处理包有了初步认识,但可能理解还不够深入,接下来我们将详细介绍。Pytorch涉及数据处理(数据装载、数据预处理、数据增强等)主要工具包及相互关系如图4-1所示。

图4-1 Pytorch主要数据处理工具
图4-1 的左边是torch.utils.data工具包,它包括以下三个类:
(1)Dataset:是一个抽象类,其它数据集需要继承这个类,并且覆写其中的两个方法(__getitem__、__len__)。
(2)DataLoader:定义一个新的迭代器,实现批量(batch)读取,打乱数据(shuffle)并提供并行加速等功能。
(3)random_split:把数据集随机拆分为给定长度的非重叠新数据集。
(4)*sampler:多种采样函数。
图4-1中间是Pytorch可视化处理工具(torchvision),Pytorch的一个视觉处理工具包,独立于Pytorch,需要另外安装,使用pip或conda安装即可:

它包括4个类,各类的主要功能如下:
(1)datasets:提供常用的数据集加载,设计上都是继承torch.utils.data.Dataset,主要包括MMIST、CIFAR10/100、ImageNet、COCO等。
(2)models:提供深度学习中各种经典的网络结构以及训练好的模型(如果选择pretrained=True),包括AlexNet, VGG系列、ResNet系列、Inception系列等。
(3)transforms:常用的数据预处理操作,主要包括对Tensor及PIL Image对象的操作。
(4)utils:含两个函数,一个是make_grid,它能将多张图片拼接在一个网格中;另一个是save_img,它能将Tensor保存成图片。

4.2 utils.data简介

utils.data包括Dataset和DataLoader。torch.utils.data.Dataset为抽象类。自定义数据集需要继承这个类,并实现两个函数。一个是__len__,另一个是__getitem__,前者提供数据的大小(size),后者通过给定索引获取数据和标签。 __getitem__一次只能获取一个数据,所以通过torch.utils.data.DataLoader来定义一个新的迭代器,实现batch读取。首先我们来定义一个简单的数据集,然后具体使用Dataset及DataLoader,以便有个直观认识。
(1) 导入需要的模块

(2)定义获取数据集的类
该类继承基类Dataset,自定义一个数据集及对应标签。

(3)获取数据集中数据

以上数据以tuple返回,每次只返回一个样本。实际上,Dateset只负责数据的抽取,一次调用__getitem__只返回一个样本。如果希望批量处理(batch),同时还要进行shuffle和并行加速等操作,可选择DataLoader。DataLoader的格式为:

主要参数说明:
 dataset: 加载的数据集;
 batch_size: 批大小;
 shuffle:是否将数据打乱;
 sampler:样本抽样
 num_workers:使用多进程加载的进程数,0代表不使用多进程;
 collate_fn:如何将多个样本数据拼接成一个batch,一般使用默认的拼接方式即可;
 pin_memory:是否将数据保存在pin memory区,pin memory中的数据转到GPU会快一些;
 drop_last:dataset 中的数据个数可能不是 batch_size的整数倍,drop_last为True会将多出来不足一个batch的数据丢弃。

运行结果
i: 0
data: tensor([[1, 2],
[3, 4]])
Label: tensor([0, 1])
i: 1
data: tensor([[2, 1],
[3, 4]])
Label: tensor([0, 1])
i: 2
data: tensor([[4, 5]])
Label: tensor([2])
从这个结果可以看出,这是批量读取。我们可以像使用迭代器一样使用它,如对它进行循环操作。不过它不是迭代器,我们可以通过iter命令转换为迭代器。

一般用data.Dataset处理同一个目录下的数据。如果数据在不同目录下,不同目录代表不同类别(这种情况比较普遍),使用data.Dataset来处理就不很方便。不过,可以使用Pytorch另一种可视化数据处理工具(即torchvision)就非常方便,不但可以自动获取标签,还提供很多数据预处理、数据增强等转换函数。

4.3 torchvision简介

torchvision有4个功能模块,model、datasets、transforms和utils。其中model后续章节将介绍,利用datasets下载一些经典数据集,3.2小节有实例,大家可以参考一下。这节我们将主要介绍如何使用datasets的ImageFolder处理自定义数据集,如何使用transforms对源数据进行预处理、增强等。下面我们重点介绍transforms及ImageFolder。

4.3.1 transforms

transforms提供了对PIL Image对象和Tensor对象的常用操作。
(1)对PIL Image的常见操作如下:
 Scale/Resize: 调整尺寸,长宽比保持不变;
 CenterCrop、RandomCrop、RandomSizedCrop:裁剪图片,CenterCrop和RandomCrop在crop时是固定size,RandomResizedCrop则是random size的crop;
 Pad: 填充;
 ToTensor: 把一个取值范围是[0,255]的PIL.Image 转换成 Tensor。形状为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloatTensor。
 RandomHorizontalFlip:图像随机水平翻转,翻转概率为0.5;
 RandomVerticalFlip: 图像随机垂直翻转;
 ColorJitter: 修改亮度、对比度和饱和度。
(2)对Tensor的常见操作如下:
 Normalize: 标准化,即减均值,除以标准差;
 ToPILImage:将Tensor转为PIL Image。
如果要对数据集进行多个操作,可通过Compose将这些操作像管道一样拼接起来,类似于nn.Sequential。以下为示例代码

还可以自己定义一个python lambda表达式,如将每个像素值加10,可表示为:transforms.Lambda(lambda x: x.add(10))。
更多内容可参考官网:
https://pytorch.org/docs/stable/torchvision/transforms.html

4.3.2 ImageFolder

当文件依据标签处于不同文件下时,如:
─── data
├── zhangliu
│ ├── 001.jpg
│ └── 002.jpg
├── wuhua
│ ├── 001.jpg
│ └── 002.jpg
.................
我们可以利用 torchvision.datasets.ImageFolder 来直接构造出 dataset,代码如下:

ImageFolder 会将目录中的文件夹名自动转化成序列,那么DataLoader载入时,标签自动就是整数序列了。
下面我们利用ImageFolder读取不同目录下图片数据,然后使用transorms进行图像预处理,预处理有多个,我们用compose把这些操作拼接在一起。然后使用DataLoader加载。
对处理后的数据用torchvision.utils中的save_image保存为一个png格式文件,然后用Image.open打开该png文件,详细代码如下:

tensor([2, 2, 0, 0, 0, 1, 2, 2])

图4-2 make_grid拼接在一起的图形
打开test01.png文件

图4-3 用Image查看png文件

4.4 可视化工具

Tensorboard是Google TensorFlow 的可视化工具,它可以记录训练数据、评估数据、网络结构、图像等,并且可以在web上展示,对于观察神经网路训练的过程非常有帮助。PyTorch可以采用tensorboard_logger, visdom等可视化工具,但这些方法比较复杂或不够友好。为解决这一问题,人们推出了可用于Pytorch可视化的新的更强大工具tensorboardX。

4.4.1 tensorboardX简介

tensorboardX功能很强大,支持scalar, image, figure, histogram, audio, text, graph, onnx_graph, embedding, pr_curve and videosummaries等可视化方式。
安装也比较方便,先安装tensorflow(CPU或GPU版),然后安装tensorboardX,在命令行运行以下命令即可。

使用tensorboardX的一般步骤为:
(1)导入tensorboardX,实例化SummaryWriter类,指明记录日志路径等信息。

【说明】
①如果是windows环境,log_dir注意路径解析,如
writer = SummaryWriter(log_dir=r'D:\myboard\test\logs')
②SummaryWriter的格式为

③如果不写log_dir,系统将在当前目录创建一个runs的目录。

(2)调用相应的API接口,接口一般格式为:

(3)启动tensorboard服务
cd到logs目录所在的同级目录,在命令行输入如下命令,logdir等式右边可以是相对路径或绝对路径。

(4)web展示
在浏览器输入

便可看到logs目录保存的各种图形,图4-4为示例图:

图4-4 tensorboardx示例图形
鼠标在图形上移动,还可以看到对应位置具体数据。
有关tensorboardX的更多内容,大家可参考其官网:
https://github.com/lanpa/tensorboardX

4.4.2用tensorboardX可视化神经网络

上节我们介绍了tensorboardX的主要内容,为帮助大家更好理解,这节我们将介绍几个实例。实例内容涉及如何使用tensorboardX可视化神经网络模型、可视化损失值、图像等。
(1)导入需要的模块

(2)构建神经网络

(3)把模型保存为graph

打开浏览器,便可看到图4-5:

图4-5 tensorboardx可视化计算图

4.4.3用tensorboardX可视化损失值

可视化损失值,使用add_scalar函数,这里利用一层全连接神经网络,训练一元二次函数的参数。

图4-6 可视化损失值与迭代步的关系

4.4.4用tensorboardX可视化特征图

利用tensorboardX对特征图进行可视化,不卷积层的特征图的抽取程度是不一样的。
x从cifair10数据集获取,具体请参考第6章代码pytorch-06。

图4-7 conv1的特征图

图4-8 conv2的特征图

4.5 小结

本章详细介绍了Pytorch有关数据下载、预处理方面的一些常用包,以及对计算结果进行可视化的工具tensorboardX,并通过一个实例详细说明如何使用tensorboardX。

《Python深度学习基于Pytorch》---目录--抢先看!

======后续将陆续加上具体内容=====
======本书使用环境:Python3.6+,Pytorch1.0+,Tensorflow1.5+,GPU或CPU(无需变更代码)
======可视化工具:Matplotlib,TensorboardX等
======本书将由工业机械出版社出版,三大部分,共16章,320页左右
======作者:吴茂贵、郁明敏、杨本法、李涛、张粤磊等

第一部分 基础篇
第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 小结
第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 小结
第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+公共安全

第26章 用预训练模型清除图像中的雾霾

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

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

第25章 Pytorch 如何高效使用GPU

深度学习涉及很多向量或多矩阵运算,如矩阵相乘、矩阵相加、矩阵-向量乘法等。深层模型的算法,如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章 轻松掌握深度学习的核心-误差反向传播法

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数据集的影响

第23章 神经风格迁移

本章数据集下载地址(提取码是: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)可视化目标图、参考风格图、生成图等。

 

第22 章 神经网络黑箱不黑

人们常说,神经网络模型就像一个“黑盒”,这对一些神经网络模型确实如此,不过卷积神经网络,在可视化方面取得长足进步,我们可以看到卷积的中间结果、可视化不同的卷积核、可视化图像中类激活的热力图(决定类分类的关键区域)。
这三种方法的具体内容为:
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)

第21章 TensorFlow又一利器Tensorflow.js(基于浏览器开发测试机器学习)

21.1 TensorFlow.js主要功能

TensorFlow.js是一个JavaScript库,它可以将机器学习功能添加到任何Web应用程序中。使用TensorFlow.js,可以从头开发机器学习脚本。你可以使用API在浏览器或Node.js服务器应用程序中构建和训练模型。并且,你可以使用TensorFlow.js在JavaScript环境中运行现有模型。
甚至,你可以使用TensorFlow.js用自己的数据再训练预先存在的机器学习模型,这些其中包括浏览器中客户端可用的数据。例如,你可以使用网络摄像头中的图像数据。如果你是一名机器学习、深度学习爱好者,那么TensorFlow.js是学习的好方法!
TensorFlow.js利用 WebGL 来进行加速的机器学习类库,它基于浏览器,提供了高层次的 JavaScript API 接口。它将高性能机器学习构建块带到您的指尖,使您能够在浏览器中训练神经网络或在推理模式下运行预先训练的模型。
TensorFlow.js的主要功能包括:利用用js开发机器学习、运行已有模型、重新训练已有模型等,具体请看下图:

有关安装/配置 TensorFlow.js 的指南,请参阅:https://js.tensorflow.org/index.html#getting-started。

21.2 安装Node.js和NPM

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 Node.js 的使用包管理器 npm来管理所有模块的安装、配置、删除等操作,使用起来非常方便。但是想要配置好npm的使用环境还是稍微有点复杂,下面跟着我一起来学习在windows系统上配置NodeJS和NPM吧。
具体安装大家可参考:
https://jingyan.baidu.com/article/48b37f8dd141b41a646488bc.html

21.3 Javascript(js)简介

1.JavaScript历史
要了解JavaScript,我们首先要回顾一下JavaScript的诞生。
在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。
由于网景公司希望能在静态HTML页面上添加一些动态效果,于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。
为什么起名叫JavaScript?原因是当时Java语言非常红火,所以网景公司希望借Java的名气来推广,但事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。
2.ECMAScript
因为网景开发了JavaScript,一年后微软又模仿JavaScript开发了JScript,为了让JavaScript成为全球标准,几个公司联合ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准。
所以简单说来就是,ECMAScript是一种语言标准,而JavaScript是网景公司对ECMAScript标准的一种实现。
那为什么不直接把JavaScript定为标准呢?因为JavaScript是网景的注册商标。
不过大多数时候,我们还是用JavaScript这个词。如果你遇到ECMAScript这个词,简单把它替换为JavaScript就行了。
3.JavaScript版本
JavaScript的标准——ECMAScript在不断发展,最新版ECMAScript 6标准(简称ES6)已经在2015年6月正式发布了,所以,讲到JavaScript的版本,实际上就是说它实现了ECMAScript标准的哪个版本。想更详细了解JavaScript,大家可参考:
http://www.runoob.com/js/js-howto.html

21.4 TensorFlow.JS基础

21.4.1 主要概念

TensorFlow.js的主要概念,包括张量(Tensor)、变量(Variables)、操作(operations)、模型和层(models and layers)等,如下图所示。

21.4.2 张量

张量(Tensor)是TensorFlow中的主要数据单位。张量包含一组数值,可以是任何形状:一维或多维。当你创建新的张量时,你还需要定义形状(shape)。你可以通过使用tensor函数并传入第二个参数来定义形状,如下所示:

这是定义具有两行四列形状的张量。产生的张量如下所示

也可以让TensorFlow推断出张量的形状,如下列代码。

我们可以使用input.shape来检索张量的大小。

这里的形状为[2]。我们还可以创建具有特定大小的张量。例如,下面我们创建一个形状为[2,2]的零值张量。

这行代码创建了以下张量
[[0,0,0],
[0,0,0]]
此外,你可以使用以下函数来增强代码可读性:

tf.scalar:只有一个值的张量
tf.tensor1d:具有一个维度的张量
tf.tensor2d:具有两个维度的张量
tf.tensor3d:具有三维的张量
tf.tensor4d:具有四个维度的张量,
下图为常见的几种张量示意图:

如表示标量:

如表示二维张量:

在TensorFlow.js中,所有张量都是不可变的。这意味着张量一旦创建,之后就无法改变。如果你执行一个更改量值的操作,总是会创建一个新的张量并返回结果值。

21.4.3 变量

张量(Tensors) 是不可变的,一旦创建,不能改变其值;而变量(variables) 则可以动态改变其值,主要用于在模型训练期间存储和更新值。您可以使用assign方法为现有变量指定新值:

变量主要用于在模型训练期间存储然后更新参数值。

21.4.4 操作

通过使用TensorFlow操作,你可以操纵张量的数据。由于张量运算的不变性,结果值总是返回一个新的张量。
TensorFlow.js提供了许多有用的操作,如square,add,sub和mul。你可以直接应用操作,如下所示:。

执行此代码后,新张量包含以下值:
[[1, 4 ],
[9, 16]]

21.4.5 内存管理

由于TensorFlow.js使用GPU来加速数学运算,因此在使用张量和变量时需要管理GPU内存。
TensorFlow.js提供了两个函数来帮助解决这个问题:dispose和tf.tidy。
 dispose
我们可以在张量或变量上调用dispose来清除它并释放其GPU内存:

在进行大量张量操作时,使用dispose会很麻烦。 TensorFlow.js提供了另一个函数tf.tidy,它与JavaScript中的常规作用域起着类似的作用,但是对于GPU支持的张量。
tf.tidy执行一个函数并清除所创建的任何中间张量,释放它们的GPU内存。 它不会清除内部函数的返回值。

使用tf.tidy将有助于防止应用程序中的内存泄漏。 它还可以用于更加谨慎地控制何时回收内存。
【注意】
 传递给tf.tidy的函数应该是同步的,也不会返回Promise。 我们建议保留更新UI的代码或在tf.tidy之外发出远程请求。
 tf.tidy不会清理变量。 变量通常持续到机器学习模型的整个生命周期,因此TensorFlow.js即使它们是在tf.tidy的情况下创建的,也不会清理它们。 但是,您可以手动调用dispose来清理。

21.4.6 模型和层

从概念上讲,模型是一种函数,给定一些输入将产生一些所需的输出。在TensorFlow.js中,有两种方法可以创建模型。 您可以直接使用ops来表示模型所做的事情。 例如:

我们也可以使用高级API tf.model来构建层中的模型,这是深度学习中的流行方法。 以下代码构造了一个tf.sequential模型:

其中units. 激活输出的数量。由于这是最后一层,这里涉及20个类别的分类任务。units的数据量指节点数。
TensorFlow.js中有许多不同类型的层。 如tf.layers.simpleRNN,tf.layers.gru和tf.layers.lstm等。
【注意】
利用TensorFlow.js构建网络时,第一层必须明确指定输入形状,其余的层默认从前面的层输入。如下示例代码:
(1)首先,用tf.sequential()实例化构建模型model
将使用Sequential模型(最简单的模型类型),其中张量将连续地从一层传递到下一层。

(2)添加一个卷积层

 nputShape.将流入模型第一层的数据的形状。这里,我们的MNIST样本是28x28像素的黑白图像。图像数据的规范格式是[row,column,depth],所以我们在这里配置的形状是[28,28,1]——每个维度有28rowX28column个像素,而depth为1是因为我们的图像只有1个颜色通道。
 kernelSize. 应用于输入数据的滑动卷积滤波器窗口的大小。在这里,我们设置kernelSize为5,它表示一个5x5的正方形卷积窗口。
 filters. 应用于输入数据,大小为kernelSize的滤波器窗口的数量。在这里,我们将对数据应用8个过滤器。
 strides. 滑动窗口的“步长” - 即每次在图像上移动时,滤波器将移动多少个像素。在这里,我们指定步幅为1,这意味着过滤器将以1像素为单位滑过图像。
 activation.卷积完成后应用于数据的 激活函数。这里,我们使用了 Rectified Linear Unit (ReLU)函数,这是ML模型中非常常见的激活函数。
 kernelInitializer. 用于随机初始化模型权重的方法,这对于训练动态是非常重要的。我们不会详细介绍初始化的细节,这里VarianceScaling是一个很不错的初始化器。

(2)添加一个池化层

 poolSize. 应用于输入数据的滑动窗口大小。在这里,我们设置poolSize为[2,2],这意味着池化层将对输入数据应用2x2窗口。
 stride. 滑动窗口的“步长” - 即每次在输入数据上移动时,窗口将移动多少个像素。在这里,我们指定[2,2]的步长,这意味着滤波器将在水平和垂直两个方向上以2个像素为单位滑过图像。
【注意】
由于poolSize和strides都是2×2,所以池窗口将完全不重叠。这意味着池化层会将前一层的激活图的大小减半。
(3)再添加一个卷积层
重复使用层结构是神经网络中的常见模式。我们添加第二个卷积层到模型,并在其后添加池化层。请注意,在我们的第二个卷积层中,我们将滤波器数量从8增加到16。还要注意,我们没有指定inputShape,因为它可以从前一层的输出形状中推断出来。

(4)添加一个展平层
我们添加一个 flatten层,将前一层的输出平铺到一个向量中。

【注意】
展平层,既没有说明输入形状,也没有说明输出形状,这些形状都是从前层输出自动获取。
(5)输出层
最后,让我们添加一个 dense层(也称为全连接层),它将执行最终的分类。 在dense层前先对卷积+池化层的输出执行flatten也是神经网络中的另一种常见模式

 units. 激活输出的数量。由于这是最后一层,我们正在做10个类别的分类任务(数字0-9),因此我们在这里使用10个units。 (有时units被称为神经元的数量)
 kernelInitializer. 我们将对dense层使用与卷积层相同的VarianceScaling初始化策略。
 activation. 分类任务的最后一层的激活函数通常是 softmax。 Softmax将我们的10维输出向量归一化为概率分布,使得我们10个类中的每个都有一个概率值。

21.4.7 优化问题

这一部分,我们将学习如何解决优化问题。给定函数f(x),我们要求求得x=a使得f(x)最小化。为此,我们需要一个优化器。优化器是一种沿着梯度来最小化函数的算法。文献中有许多优化器,如SGD,Adam等等,这些优化器的速度和准确性各不相同。Tensorflowjs支持大多数重要的优化器。
我们将举一个简单的例子:f(x)=x⁶+2x⁴+3x²+x+1。函数的曲线图如下所示。可以看到函数的最小值在区间[-0.5,0]。我们将使用优化器来找出确切的值。

首先,我们定义要最小化的函数:

现在我们可以迭代地最小化函数以找到最小值。我们将以a=2的初始值开始,学习率定义了达到最小值的速度。我们将使用Adam优化器:

使用值为0.9的学习速率,我们发现200次迭代后的最小值对应的y为-0.16092407703399658。

更多内容可参考:
https://js.tensorflow.org/tutorials/core-concepts.html

21.5设置项目(使用npm)

(1)在第一步中,我们需要设置项目。创建一个新的空目录。

(2)切换到新创建的项目文件夹

以下操作,都在该文件夹下
(3)创建一个package.json文件
在文件夹中,我们现在准备创建一个package.json文件,以便我们能够通过使用Node.js包管理器来管理依赖项:

(4)安装Parcel捆绑器
因为我们将在项目文件夹中本地安装依赖项(例如Tensorflow.js库),所以我们需要为Web应用程序使用模块捆绑器(bundler)。为了尽可能简单,我们将使用Parcel Web应用程序捆绑器,因为Parcel不需要进行配置。让我们通过在项目目录中执行以下命令来安装Parcel捆绑器:

(5)创建两个空文件
接下来,让我们为我们的实现创建两个新的空文件:

(6)安装Bootstrap库
我们将Bootstrap库添加为依赖项,因为我们将为我们的用户界面元素使用一些Bootstrap CSS类:

(7)修改两个空文件
在index.html中,让我们插入以下基本html页面的代码:

另外,将以下代码添加到index.js

我们将文本Hello World写入具有ID输出的元素,以在屏幕上查看第一个结果并获得正确处理JS代码的确认。

(8)启动程序及web服务
最后,让我们通过使用parcel命令启动构建程序和开发的Web服务:

你现在应该可以在浏览器中通过URL http://localhost:1234打开网站。结果应与你在以下截图中看到的内容对应:

【注意】
以上步骤我们也可用yarn执行,npm与yarn的对应关系及优缺点,可参考:
https://juejin.im/entry/5a73ca7d6fb9a063435ea9ad
http://www.fly63.com/article/detial/554

21.6设置项目(使用yarn)

(1)在第一步中,我们需要设置项目。创建一个新的空目录。

(2)切换到新创建的项目文件夹

以下操作,都在该文件夹下
(3)创建一个package.json文件
在文件夹中,我们现在准备创建一个package.json文件,以便我们能够通过使用Node.js包管理器来管理依赖项:

(4)安装Parcel捆绑器
Parcel 是一个 web 应用打包工具, 与其他工具的区别在于开发者的使用体验。它利用多核处理器提供了极快的速度, 并且不需要任何配置。
因为我们将在项目文件夹中本地安装依赖项(例如Tensorflow.js库),所以我们需要为Web应用程序使用模块捆绑器(bundler)。为了尽可能简单,我们将使用Parcel Web应用程序捆绑器,因为Parcel不需要进行配置。让我们通过在项目目录中执行以下命令来安装Parcel捆绑器:

(5)创建两个空文件
接下来,让我们为我们的实现创建两个新的空文件:

(6)安装Bootstrap库
我们将Bootstrap库添加为依赖项,因为我们将为我们的用户界面元素使用一些Bootstrap CSS类:

将生成一个文件(yarn.lock)和一个文件夹(node_modules)
(7)修改两个空文件
在index.html中,让我们插入以下基本html页面的代码:

另外,将以下代码添加到index.js

我们将文本Hello World写入具有ID输出的元素,以在屏幕上查看第一个结果并获得正确处理JS代码的确认。

(8)启动程序及web服务
最后,让我们通过使用parcel命令启动构建程序和开发的Web服务:

执行该命令,将生成一个dist文件夹,同时更新相关文件。
你现在应该可以在浏览器中通过URL http://localhost:1234打开网站。当文件改变时它仍然会自动重建并支持热替换。结果应与你在以下截图中看到的内容对应:

【说明】

21.7实例详解

本实例利用tensorflow.js定义一个模型,该模型模拟一条直线(y=2x-1),然后,根据训练好的模型,在浏览器上,输入一个值,实时预测值,新体验,很不错哦!
(1)添加ensorflow.js
为了Tensorflow.js添加到项目中,我们再次使用NPM并在项目目录中执行以下命令

这将下载并将其安装到node_modules文件夹中。成功执行此命令后,我们现在可以通过在文件顶部添加以下import语句来导入index.js中的Tensorflow.js库:

当我们将TensorFlow.js导入为tf后,我们就可以通过在代码中使用tf对象来访问TensorFlow.js API。
(2)定义模型
现在TensorFlow.js已经可用,让我们从一个简单的机器学习练习开始。下面的示例应用程序涵盖的机器学习脚本是公式Y = 2X-1,这是个线性回归。
此函数返回给定X对应的Y值。如果绘制点(X,Y),你将得到一条直线,如下所示:

接下来我们将使用来自该函数的输入数据(X,Y)并使用这些数字训练模型。然后使用训练好的模型,根据新的X值来预测Y值。期望从模型返回的Y结果接近函数返回的精确值。

让我们创建一个非常简单的神经网络来实现。此模型只需处理一个输入值和一个输出值:

首先,我们通过调用tf.sequential方法创建一个新的模型实例。得到一个新的序列模型。其中一层的输出是下一层的输入,即模型是层的简单“堆叠”,没有分支或跳过。
创建好模型后,我们准备通过调用model.add来添加第一层。通过调用tf.layers.dense将新层传递给add方法。这会创建一个稠密层或全连接层。在稠密层中,层中的每个节点都连接到前一层中的每个节点。对于我们的示例,只需向神经网络添加一个具有一个输入和输出形状的密集层就足够了。

在下一步中,我们需要为模型指定损失函数和优化函数。

通过将配置对象传递给模型实例的编译方法来完成。配置对象包含两个属性:
 loss:这里我们使用meanSquaredError损失函数。通常,损失函数用于将一个或多个变量的值映射到表示与该值相关联的一些“损失”的实数上。如果训练模型,它会尝试最小化损失函数的结果。估计量的均方误差是误差平方的平均值 - 即估计值与估计值之间的平均平方差。
 optimizer:要使用的优化器函数。我们的线性回归机器学习任务使用的是sgd函数。Sgd代表Stochastic Gradient Descent,它是一个适用于线性回归任务的优化器函数。
现在模型已配置完成,接下来将训练模型。
(3)训练模型
为了用函数Y=2X-1的值训练模型,我们定义了两个形状为6,1的张量。第一张量xs包含x值,第二张量ys包含相应的y值:

把这两个张量传递给调用的model.fit方法来训练模型

对于第三个参数,我们传递一个对象,该对象包含一个名为epochs的属性,该属性设置为值500。此处指定的数字是指定TensorFlow.js通过训练集的次数。
fit方法的结果是一个Promise,所以我们注册一个回调函数,该函数在训练结束时被激活。
(4)预测
现在让我们在这个回调函数中执行最后一步,并根据给定的x值预测y值

使用model.predict方法完成预测。该方法以张量的形式接收输入值作为参数。在这个特定情况下,我们在内部创建一个只有一个值(5)的张量并将其传递给预测。通过调用print函数,我们确保将结果值打印到控制台,如下所示:

输出显示预测值为8.9962864并且非常接近9(如果x设置为5,函数Y=2X-1的Y值为9)
(5)优化界面
已上面经实现的示例是使用固定输入值进行预测(5)并将结果输出到浏览器控制台。让我们引入一个更复杂的用户界面,让用户能够输入用于预测的值。在index.html中添加以下代码:

这里我们使用各种Bootstrap CSS类,向页面添加输入和按钮元素,并定义用于输出结果的区域。

我们还需要在index.js中做一些更改:

注册了预测按钮的click事件的事件处理程序。在此函数内部,读取input元素的值(通过number函数把输入值转换为数据类型)并调用model.predict方法。此方法返回的结果将插入具有id输出的元素中。

现在的结果应该如下所示:

现在我们根据训练好的模型,输入值(x),就可实时预测Y值了!。单击“ Predict ”按钮完成预测。结果会直接显示在网站上。