diff --git a/机器学习/ApacheCN/apachecn-dl-zh/README.md b/机器学习/ApacheCN/apachecn-dl-zh/README.md deleted file mode 100644 index 7f237198..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# ApacheCN 深度学习译文集 - -> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) -> -> 自豪地采用[谷歌翻译](https://translate.google.cn/) -> -> 不要担心自己的形象,只关心如何实现目标。——《原则》,生活原则 2.3.c - -* [在线阅读](https://dl.apachecn.org) -* [在线阅读(Gitee)](https://apachecn.gitee.io/apachecn-dl-zh/) -* [ApacheCN 面试求职交流群 724187166](https://jq.qq.com/?_wv=1027&k=54ujcL3) -* [ApacheCN 学习资源](http://www.apachecn.org/) - -## 目录 - -+ [Sklearn 与 TensorFlow 机器学习实用指南第二版](docs/hands-on-ml-2e-zh/SUMMARY.md) -+ [PyTorch 自然语言处理](docs/nlp-pytorch-zh/SUMMARY.md) -+ [TensorFlow 1.x 深度学习秘籍](docs/tf-1x-dl-cookbook/SUMMARY.md) -+ [PyTorch 中文官方教程 1.7](docs/pt-tut-17/SUMMARY.md) -+ [使用 TensorFlow 构建机器学习项目中文版](docs/build-ml-proj-tf-zh/SUMMARY.md) -+ [TensorFlow 深度学习中文第二版](docs/dl-tf-2e-zh/SUMMARY.md) -+ [TensorFlow 深度学习实战指南中文版](docs/hands-on-dl-tf-zh/SUMMARY.md) -+ [精通 TensorFlow 1.x](docs/mastering-tf-1x-zh/SUMMARY.md) -+ [TensorFlow 机器学习秘籍中文第二版](docs/tf-ml-cookbook-2e-zh/SUMMARY.md) -+ [与 TensorFlow 的初次接触](docs/first_contact_with_tensorFlow/SUMMARY.md) -+ [TensorFlow 学习指南](docs/learning-tf-zh/SUMMARY.md) -+ [TensorFlow Rager 教程](docs/tf-eager-tut/SUMMARY.md) -+ [TensorFlow 高效编程](docs/effective-tf.md) -+ [图嵌入综述:问题,技术与应用](docs/ge-survey-arxiv-1709-07604-zh/SUMMARY.md) -+ [基于深度学习的推荐系统:综述和新视角](docs/rs-survey-arxiv-1707-07435-zh/SUMMARY.md) -+ [关于卷积神经网络我们理解了什么](docs/what-do-we-understand-about-convnet/SUMMARY.md) -+ [机器学习超级复习笔记](docs/super-machine-learning-revision-notes/SUMMARY.md) -+ [Python 迁移学习实用指南](docs/handson-tl-py/SUMMARY.md) -+ [面向计算机视觉的深度学习](docs/dl-cv/SUMMARY.md) -+ [深度学习快速参考](docs/dl-quick-ref/SUMMARY.md) -+ [TensorFlow 2.0 快速入门指南](docs/tf-20-quick-start-guide/SUMMARY.md) -+ [TensorFlow 入门](docs/get-start-tf/SUMMARY.md) -+ [TensorFlow 卷积神经网络实用指南](docs/handson-cnn-tf/SUMMARY.md) -+ [Python 人工智能中文版](docs/ai-py/SUMMARY.md) -+ [Python 无监督学习实用指南](docs/handson-unsup-learn-py/SUMMARY.md) -+ [生成对抗网络项目](docs/gan-proj/SUMMARY.md) -+ [TensorFlow 智能移动项目](docs/intel-mobi-proj-tf/SUMMARY.md) -+ [TensorFlow 和 Keras 应用开发入门](docs/begin-app-dev-tf-keras/SUMMARY.md) -+ [TensorFlow 图像深度学习实用指南](docs/handson-dl-img-tf/SUMMARY.md) -+ [Python 元学习实用指南](docs/handson-meta-learn-py/SUMMARY.md) -+ [Python 强化学习实用指南](docs/handson-rl-py/SUMMARY.md) -+ [Python 智能项目](docs/intel-proj-py/SUMMARY.md) -+ [精通 Sklearn 和 TensorFlow 预测性分析](docs/master-pred-anal-sklearn-tf/SUMMARY.md) -+ [TensorFlow 2.0 的新增功能](docs/whats-new-tf2/SUMMARY.md) -+ [UCB CS294-112 深度强化学习中文笔记](docs/ucb-cs294-112-notes-zh/SUMMARY.md) -+ [TensorFlow 2 和 Keras 高级深度学习](docs/adv-dl-tf2-keras/SUMMARY.md) -+ [GCP 上的人工智能实用指南](docs/handson-ai-gcp/SUMMARY.md) -+ [Python 深度学习架构实用指南](docs/handson-dl-arch-py/SUMMARY.md) -+ [Python Web 深度学习实用指南](docs/handson-py-dl-web/SUMMARY.md) -+ [精通 TensorFlow 2.x 计算机视觉](docs/master-cv-tf-2x/SUMMARY.md) -+ [TensorFlow Lite,ML Kit 和 Flutter 移动深度学习](docs/mobi-dl-tflite/SUMMARY.md) -+ [PyTorch 人工智能研讨会](docs/dl-pt-workshop/SUMMARY.md) -+ [Python 一次学习实用指南](docs/handson-1shot-learn-py/SUMMARY.md) -+ [Python 自然语言处理实用指南](docs/handson-nlp-pt-1x/SUMMARY.md) -+ [PyTorch 人工智能基础知识](docs/pt-ai-fund/SUMMARY.md) -+ [PyTorch 深度学习实用指南](docs/pt-dl-handson/SUMMARY.md) -+ [TensorFlow 强化学习](docs/rl-tf/SUMMARY.md) - -## 下载 - -### Docker - -``` -docker pull apachecn0/apachecn-dl-zh -docker run -tid -p :80 apachecn0/apachecn-dl-zh -# 访问 http://localhost:{port} 查看文档 -``` - -### PYPI - -``` -pip install apachecn-dl-zh -apachecn-dl-zh -# 访问 http://localhost:{port} 查看文档 -``` - -### NPM - -``` -npm install -g apachecn-dl-zh -apachecn-dl-zh -# 访问 http://localhost:{port} 查看文档 -``` - -## 贡献指南 - -本项目需要校对,欢迎大家提交 Pull Request。 - -> 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并不要求您做到十全十美,因此请不要担心因为翻译上犯错——在大部分情况下,我们的服务器已经记录所有的翻译,因此您不必担心会因为您的失误遭到无法挽回的破坏。(改编自维基百科) - -## 联系方式 - -### 负责人 - -* [飞龙](https://github.com/wizardforcel): 562826179 - -### 其他 - -* 在我们的 [apachecn/apachecn-tf-zh](https://github.com/apachecn/apachecn-tf-zh) github 上提 issue. -* 发邮件到 Email: `apachecn@163.com`. -* 在我们的 [组织学习交流群](http://www.apachecn.org/organization/348.html) 中联系群主/管理员即可. - -## 赞助我们 - -![](http://data.apachecn.org/img/about/donate.jpg) diff --git a/机器学习/ApacheCN/apachecn-dl-zh/SUMMARY.md b/机器学习/ApacheCN/apachecn-dl-zh/SUMMARY.md deleted file mode 100644 index e7dfd44e..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/SUMMARY.md +++ /dev/null @@ -1,613 +0,0 @@ -+ [Sklearn 与 TensorFlow 机器学习实用指南第二版](docs/hands-on-ml-2e-zh/README.md) - + [零、前言](docs/hands-on-ml-2e-zh/0.md) - + [一、机器学习概览](docs/hands-on-ml-2e-zh/1.md) - + [二、端到端的机器学习项目](docs/hands-on-ml-2e-zh/2.md) - + [三、分类](docs/hands-on-ml-2e-zh/3.md) - + [四、训练模型](docs/hands-on-ml-2e-zh/4.md) - + [五、支持向量机](docs/hands-on-ml-2e-zh/5.md) - + [六、决策树](docs/hands-on-ml-2e-zh/6.md) - + [七、集成学习和随机森林](docs/hands-on-ml-2e-zh/7.md) - + [八、降维](docs/hands-on-ml-2e-zh/8.md) - + [十、使用 Keras 搭建人工神经网络](docs/hands-on-ml-2e-zh/10.md) - + [十一、训练深度神经网络](docs/hands-on-ml-2e-zh/11.md) - + [十二、使用 TensorFlow 自定义模型并训练](docs/hands-on-ml-2e-zh/12.md) - + [十三、使用 TensorFlow 加载和预处理数据](docs/hands-on-ml-2e-zh/13.md) - + [十四、使用卷积神经网络实现深度计算机视觉](docs/hands-on-ml-2e-zh/14.md) - + [十五、使用 RNN 和 CNN 处理序列](docs/hands-on-ml-2e-zh/15.md) - + [十六、使用 RNN 和注意力机制进行自然语言处理](docs/hands-on-ml-2e-zh/16.md) - + [十七、使用自编码器和 GAN 做表征学习和生成式学习](docs/hands-on-ml-2e-zh/17.md) - + [十八、强化学习](docs/hands-on-ml-2e-zh/18.md) - + [十九、规模化训练和部署 TensorFlow 模型](docs/hands-on-ml-2e-zh/19.md) -+ [PyTorch 自然语言处理](docs/nlp-pytorch-zh/README.md) - + [一、基础介绍](docs/nlp-pytorch-zh/1.md) - + [二、传统 NLP 快速回顾](docs/nlp-pytorch-zh/2.md) - + [三、神经网络基础组件](docs/nlp-pytorch-zh/3.md) - + [四、自然语言处理的前馈网络](docs/nlp-pytorch-zh/4.md) - + [五、嵌入单词和类型](docs/nlp-pytorch-zh/5.md) - + [六、自然语言处理的序列模型](docs/nlp-pytorch-zh/6.md) - + [七、自然语言处理的进阶序列模型](docs/nlp-pytorch-zh/7.md) - + [八、自然语言处理的高级序列模型](docs/nlp-pytorch-zh/8.md) - + [九、经典, 前沿和后续步骤](docs/nlp-pytorch-zh/9.md) -+ [TensorFlow 1.x 深度学习秘籍](docs/tf-1x-dl-cookbook/README.md) - + [零、前言](docs/tf-1x-dl-cookbook/00.md) - + [一、TensorFlow 简介](docs/tf-1x-dl-cookbook/01.md) - + [二、回归](docs/tf-1x-dl-cookbook/02.md) - + [三、神经网络:感知器](docs/tf-1x-dl-cookbook/03.md) - + [四、卷积神经网络](docs/tf-1x-dl-cookbook/04.md) - + [五、高级卷积神经网络](docs/tf-1x-dl-cookbook/05.md) - + [六、循环神经网络](docs/tf-1x-dl-cookbook/06.md) - + [七、无监督学习](docs/tf-1x-dl-cookbook/07.md) - + [八、自编码器](docs/tf-1x-dl-cookbook/08.md) - + [九、强化学习](docs/tf-1x-dl-cookbook/09.md) - + [十、移动计算](docs/tf-1x-dl-cookbook/10.md) - + [十一、生成模型和 CapsNet](docs/tf-1x-dl-cookbook/11.md) - + [十二、分布式 TensorFlow 和云深度学习](docs/tf-1x-dl-cookbook/12.md) - + [十三、AutoML 和学习如何学习(元学习)](docs/tf-1x-dl-cookbook/13.md) - + [十四、TensorFlow 处理单元](docs/tf-1x-dl-cookbook/14.md) -+ [PyTorch 中文官方教程 1.7](docs/pt-tut-17/README.md) - + [学习 PyTorch](docs/pt-tut-17/01.md) - + [PyTorch 深度学习:60 分钟的突击](docs/pt-tut-17/02.md) - + [张量](docs/pt-tut-17/03.md) - + [`torch.autograd`的简要介绍](docs/pt-tut-17/04.md) - + [神经网络](docs/pt-tut-17/05.md) - + [训练分类器](docs/pt-tut-17/06.md) - + [通过示例学习 PyTorch](docs/pt-tut-17/07.md) - + [热身:NumPy](docs/pt-tut-17/08.md) - + [PyTorch:张量](docs/pt-tut-17/09.md) - + [PyTorch:张量和 Autograd](docs/pt-tut-17/10.md) - + [PyTorch:定义新的 Autograd 函数](docs/pt-tut-17/11.md) - + [PyTorch:`nn`](docs/pt-tut-17/12.md) - + [PyTorch:`optim`](docs/pt-tut-17/13.md) - + [PyTorch:自定义`nn`模块](docs/pt-tut-17/14.md) - + [PyTorch:控制流 + 权重共享](docs/pt-tut-17/15.md) - + [`torch.nn`到底是什么?](docs/pt-tut-17/16.md) - + [使用 TensorBoard 可视化模型,数据和训练](docs/pt-tut-17/17.md) - + [图片/视频](docs/pt-tut-17/18.md) - + [`torchvision`对象检测微调教程](docs/pt-tut-17/19.md) - + [计算机视觉的迁移学习教程](docs/pt-tut-17/20.md) - + [对抗示例生成](docs/pt-tut-17/21.md) - + [DCGAN 教程](docs/pt-tut-17/22.md) - + [音频](docs/pt-tut-17/23.md) - + [音频 I/O 和`torchaudio`的预处理](docs/pt-tut-17/24.md) - + [使用`torchaudio`的语音命令识别](docs/pt-tut-17/25.md) - + [文本](docs/pt-tut-17/26.md) - + [使用`nn.Transformer`和`torchtext`的序列到序列建模](docs/pt-tut-17/27.md) - + [从零开始的 NLP:使用字符级 RNN 分类名称](docs/pt-tut-17/28.md) - + [从零开始的 NLP:使用字符级 RNN 生成名称](docs/pt-tut-17/29.md) - + [从零开始的 NLP:使用序列到序列网络和注意力的翻译](docs/pt-tut-17/30.md) - + [使用`torchtext`的文本分类](docs/pt-tut-17/31.md) - + [`torchtext`语言翻译](docs/pt-tut-17/32.md) - + [强化学习](docs/pt-tut-17/33.md) - + [强化学习(DQN)教程](docs/pt-tut-17/34.md) - + [训练玩马里奥的 RL 智能体](docs/pt-tut-17/35.md) - + [在生产中部署 PyTorch 模型](docs/pt-tut-17/36.md) - + [通过使用 Flask 的 REST API 在 Python 中部署 PyTorch](docs/pt-tut-17/37.md) - + [TorchScript 简介](docs/pt-tut-17/38.md) - + [在 C++ 中加载 TorchScript 模型](docs/pt-tut-17/39.md) - + [将模型从 PyTorch 导出到 ONNX 并使用 ONNX 运行时运行它(可选)](docs/pt-tut-17/40.md) - + [前端 API](docs/pt-tut-17/41.md) - + [PyTorch 中的命名张量简介(原型)](docs/pt-tut-17/42.md) - + [PyTorch 中通道在最后的内存格式(beta)](docs/pt-tut-17/43.md) - + [使用 PyTorch C++ 前端](docs/pt-tut-17/44.md) - + [自定义 C++ 和 CUDA 扩展](docs/pt-tut-17/45.md) - + [使用自定义 C++ 运算符扩展 TorchScript](docs/pt-tut-17/46.md) - + [使用自定义 C++ 类扩展 TorchScript](docs/pt-tut-17/47.md) - + [TorchScript 中的动态并行性](docs/pt-tut-17/48.md) - + [C++ 前端中的 Autograd](docs/pt-tut-17/49.md) - + [在 C++ 中注册调度运算符](docs/pt-tut-17/50.md) - + [模型优化](docs/pt-tut-17/51.md) - + [分析您的 PyTorch 模块](docs/pt-tut-17/52.md) - + [使用 Ray Tune 的超参数调整](docs/pt-tut-17/53.md) - + [模型剪裁教程](docs/pt-tut-17/54.md) - + [LSTM 单词语言模型上的动态量化(beta)](docs/pt-tut-17/55.md) - + [BERT 上的动态量化(Beta)](docs/pt-tut-17/56.md) - + [PyTorch 中使用 Eager 模式的静态量化(beta)](docs/pt-tut-17/57.md) - + [计算机视觉的量化迁移学习教程(beta)](docs/pt-tut-17/58.md) - + [并行和分布式训练](docs/pt-tut-17/59.md) - + [PyTorch 分布式概述](docs/pt-tut-17/60.md) - + [单机模型并行最佳实践](docs/pt-tut-17/61.md) - + [分布式数据并行入门](docs/pt-tut-17/62.md) - + [用 PyTorch 编写分布式应用](docs/pt-tut-17/63.md) - + [分布式 RPC 框架入门](docs/pt-tut-17/64.md) - + [使用分布式 RPC 框架实现参数服务器](docs/pt-tut-17/65.md) - + [使用 RPC 的分布式管道并行化](docs/pt-tut-17/66.md) - + [使用异步执行实现批量 RPC 处理](docs/pt-tut-17/67.md) - + [将分布式`DataParallel`与分布式 RPC 框架相结合](docs/pt-tut-17/68.md) -+ [使用 TensorFlow 构建机器学习项目中文版](docs/build-ml-proj-tf-zh/README.md) - + [一、探索和转换数据](docs/build-ml-proj-tf-zh/ch01.md) - + [二、聚类](docs/build-ml-proj-tf-zh/ch02.md) - + [三、线性回归](docs/build-ml-proj-tf-zh/ch03.md) - + [四、逻辑回归](docs/build-ml-proj-tf-zh/ch04.md) - + [五、简单的前馈神经网络](docs/build-ml-proj-tf-zh/ch05.md) - + [六、卷积神经网络](docs/build-ml-proj-tf-zh/ch06.md) - + [七、循环神经网络和 LSTM](docs/build-ml-proj-tf-zh/ch07.md) - + [八、深度神经网络](docs/build-ml-proj-tf-zh/ch08.md) - + [九、大规模运行模型 -- GPU 和服务](docs/build-ml-proj-tf-zh/ch09.md) - + [十、库安装和其他提示](docs/build-ml-proj-tf-zh/ch10.md) -+ [TensorFlow 深度学习中文第二版](docs/dl-tf-2e-zh/README.md) - + [一、人工神经网络](docs/dl-tf-2e-zh/ch01.md) - + [二、TensorFlow v1.6 的新功能是什么?](docs/dl-tf-2e-zh/ch02.md) - + [三、实现前馈神经网络](docs/dl-tf-2e-zh/ch03.md) - + [四、CNN 实战](docs/dl-tf-2e-zh/ch04.md) - + [五、使用 TensorFlow 实现自编码器](docs/dl-tf-2e-zh/ch05.md) - + [六、RNN 和梯度消失或爆炸问题](docs/dl-tf-2e-zh/ch06.md) - + [七、TensorFlow GPU 配置](docs/dl-tf-2e-zh/ch07.md) - + [八、TFLearn](docs/dl-tf-2e-zh/ch08.md) - + [九、使用协同过滤的电影推荐](docs/dl-tf-2e-zh/ch09.md) - + [十、OpenAI Gym](docs/dl-tf-2e-zh/ch10.md) -+ [TensorFlow 深度学习实战指南中文版](docs/hands-on-dl-tf-zh/README.md) - + [一、入门](docs/hands-on-dl-tf-zh/ch01.md) - + [二、深度神经网络](docs/hands-on-dl-tf-zh/ch02.md) - + [三、卷积神经网络](docs/hands-on-dl-tf-zh/ch03.md) - + [四、循环神经网络介绍](docs/hands-on-dl-tf-zh/ch04.md) - + [五、总结](docs/hands-on-dl-tf-zh/ch05.md) -+ [精通 TensorFlow 1.x](docs/mastering-tf-1x-zh/README.md) - + [一、TensorFlow 101](docs/mastering-tf-1x-zh/ch01.md) - + [二、TensorFlow 的高级库](docs/mastering-tf-1x-zh/ch02.md) - + [三、Keras 101](docs/mastering-tf-1x-zh/ch03.md) - + [四、TensorFlow 中的经典机器学习](docs/mastering-tf-1x-zh/ch04.md) - + [五、TensorFlow 和 Keras 中的神经网络和 MLP](docs/mastering-tf-1x-zh/ch05.md) - + [六、TensorFlow 和 Keras 中的 RNN](docs/mastering-tf-1x-zh/ch06.md) - + [七、TensorFlow 和 Keras 中的用于时间序列数据的 RNN](docs/mastering-tf-1x-zh/ch07.md) - + [八、TensorFlow 和 Keras 中的用于文本数据的 RNN](docs/mastering-tf-1x-zh/ch08.md) - + [九、TensorFlow 和 Keras 中的 CNN](docs/mastering-tf-1x-zh/ch09.md) - + [十、TensorFlow 和 Keras 中的自编码器](docs/mastering-tf-1x-zh/ch10.md) - + [十一、TF 服务:生产中的 TensorFlow 模型](docs/mastering-tf-1x-zh/ch11.md) - + [十二、迁移学习和预训练模型](docs/mastering-tf-1x-zh/ch12.md) - + [十三、深度强化学习](docs/mastering-tf-1x-zh/ch13.md) - + [十四、生成对抗网络](docs/mastering-tf-1x-zh/ch14.md) - + [十五、TensorFlow 集群的分布式模型](docs/mastering-tf-1x-zh/ch15.md) - + [十六、移动和嵌入式平台上的 TensorFlow 模型](docs/mastering-tf-1x-zh/ch16.md) - + [十七、R 中的 TensorFlow 和 Keras](docs/mastering-tf-1x-zh/ch17.md) - + [十八、调试 TensorFlow 模型](docs/mastering-tf-1x-zh/ch18.md) - + [十九、张量处理单元](docs/mastering-tf-1x-zh/ch19.md) -+ [TensorFlow 机器学习秘籍中文第二版](docs/tf-ml-cookbook-2e-zh/README.md) - + [一、TensorFlow 入门](docs/tf-ml-cookbook-2e-zh/ch01.md) - + [二、TensorFlow 的方式](docs/tf-ml-cookbook-2e-zh/ch02.md) - + [三、线性回归](docs/tf-ml-cookbook-2e-zh/ch03.md) - + [四、支持向量机](docs/tf-ml-cookbook-2e-zh/ch04.md) - + [五、最近邻方法](docs/tf-ml-cookbook-2e-zh/ch05.md) - + [六、神经网络](docs/tf-ml-cookbook-2e-zh/ch06.md) - + [七、自然语言处理](docs/tf-ml-cookbook-2e-zh/ch07.md) - + [八、卷积神经网络](docs/tf-ml-cookbook-2e-zh/ch08.md) - + [九、循环神经网络](docs/tf-ml-cookbook-2e-zh/ch09.md) - + [十、将 TensorFlow 投入生产](docs/tf-ml-cookbook-2e-zh/ch10.md) - + [十一、更多 TensorFlow](docs/tf-ml-cookbook-2e-zh/ch11.md) -+ [与 TensorFlow 的初次接触](docs/first_contact_with_tensorFlow/README.md) - + [前言](docs/first_contact_with_tensorFlow/0.md) - + [1. TensorFlow 基础知识](docs/first_contact_with_tensorFlow/1.md) - + [2. TensorFlow 中的线性回归](docs/first_contact_with_tensorFlow/2.md) - + [3. TensorFlow 中的聚类](docs/first_contact_with_tensorFlow/3.md) - + [4. TensorFlow 中的单层神经网络](docs/first_contact_with_tensorFlow/4.md) - + [5. TensorFlow 中的多层神经网络](docs/first_contact_with_tensorFlow/5.md) - + [6. 并行](docs/first_contact_with_tensorFlow/6.md) - + [后记](docs/first_contact_with_tensorFlow/7.md) -+ [TensorFlow 学习指南](docs/learning-tf-zh/README.md) - + [一、基础](docs/learning-tf-zh/1.md) - + [二、线性模型](docs/learning-tf-zh/2.md) - + [三、学习](docs/learning-tf-zh/3.md) - + [四、分布式](docs/learning-tf-zh/4.md) -+ [TensorFlow Rager 教程](docs/tf-eager-tut/README.md) - + [一、如何使用 TensorFlow Eager 构建简单的神经网络](docs/tf-eager-tut/1.md) - + [二、在 Eager 模式中使用指标](docs/tf-eager-tut/2.md) - + [三、如何保存和恢复训练模型](docs/tf-eager-tut/3.md) - + [四、文本序列到 TFRecords](docs/tf-eager-tut/4.md) - + [五、如何将原始图片数据转换为 TFRecords](docs/tf-eager-tut/5.md) - + [六、如何使用 TensorFlow Eager 从 TFRecords 批量读取数据](docs/tf-eager-tut/6.md) - + [七、使用 TensorFlow Eager 构建用于情感识别的卷积神经网络(CNN)](docs/tf-eager-tut/7.md) - + [八、用于 TensorFlow Eager 序列分类的动态循坏神经网络](docs/tf-eager-tut/8.md) - + [九、用于 TensorFlow Eager 时间序列回归的递归神经网络](docs/tf-eager-tut/9.md) -+ [TensorFlow 高效编程](docs/effective-tf.md) -+ [图嵌入综述:问题,技术与应用](docs/ge-survey-arxiv-1709-07604-zh/README.md) - + [一、引言](docs/ge-survey-arxiv-1709-07604-zh/1.md) - + [三、图嵌入的问题设定](docs/ge-survey-arxiv-1709-07604-zh/2.md) - + [四、图嵌入技术](docs/ge-survey-arxiv-1709-07604-zh/3.md) - + [基于边重构的优化问题](docs/ge-survey-arxiv-1709-07604-zh/4.md) - + [应用](docs/ge-survey-arxiv-1709-07604-zh/5.md) -+ [基于深度学习的推荐系统:综述和新视角](docs/rs-survey-arxiv-1707-07435-zh/README.md) - + [引言](docs/rs-survey-arxiv-1707-07435-zh/1.md) - + [基于深度学习的推荐:最先进的技术](docs/rs-survey-arxiv-1707-07435-zh/2.md) - + [基于卷积神经网络的推荐](docs/rs-survey-arxiv-1707-07435-zh/3.md) -+ [关于卷积神经网络我们理解了什么](docs/what-do-we-understand-about-convnet/README.md) - + [第1章概论](docs/what-do-we-understand-about-convnet/1.md) - + [第2章多层网络](docs/what-do-we-understand-about-convnet/2.1.1-2.1.3.md) - + [2.1.4生成对抗网络](docs/what-do-we-understand-about-convnet/2.1.4-2.1.6.md) - + [2.2.1最近ConvNets演变中的关键架构](docs/what-do-we-understand-about-convnet/2.2.1.md) - + [2.2.2走向ConvNet不变性](docs/what-do-we-understand-about-convnet/2.2.2-2.2.3.md) - + [2.3时空卷积网络](docs/what-do-we-understand-about-convnet/2.3-2.4.md) - + [第3章了解ConvNets构建块](docs/what-do-we-understand-about-convnet/3.1.md) - + [3.2整改](docs/what-do-we-understand-about-convnet/3.2.md) - + [3.3规范化](docs/what-do-we-understand-about-convnet/3.3.md) - + [3.4汇集](docs/what-do-we-understand-about-convnet/3.4-3.5.md) - + [第四章现状](docs/what-do-we-understand-about-convnet/4.1.md) - + [4.2打开问题](docs/what-do-we-understand-about-convnet/4.2.md) - + [参考](docs/what-do-we-understand-about-convnet/ref.md) -+ [机器学习超级复习笔记](docs/super-machine-learning-revision-notes/README.md) -+ [Python 迁移学习实用指南](docs/handson-tl-py/README.md) - + [零、前言](docs/handson-tl-py/0.md) - + [一、机器学习基础](docs/handson-tl-py/1.md) - + [二、深度学习基础](docs/handson-tl-py/2.md) - + [三、了解深度学习架构](docs/handson-tl-py/3.md) - + [四、迁移学习基础](docs/handson-tl-py/4.md) - + [五、释放迁移学习的力量](docs/handson-tl-py/5.md) - + [六、图像识别与分类](docs/handson-tl-py/6.md) - + [七、文本文件分类](docs/handson-tl-py/7.md) - + [八、音频事件识别与分类](docs/handson-tl-py/8.md) - + [九、DeepDream](docs/handson-tl-py/9.md) - + [十、自动图像字幕生成器](docs/handson-tl-py/10.md) - + [十一、图像着色](docs/handson-tl-py/11.md) -+ [面向计算机视觉的深度学习](docs/dl-cv/README.md) - + [零、前言](docs/dl-cv/00.md) - + [一、入门](docs/dl-cv/01.md) - + [二、图像分类](docs/dl-cv/02.md) - + [三、图像检索](docs/dl-cv/03.md) - + [四、对象检测](docs/dl-cv/04.md) - + [五、语义分割](docs/dl-cv/05.md) - + [六、相似性学习](docs/dl-cv/06.md) - + [七、图像字幕](docs/dl-cv/07.md) - + [八、生成模型](docs/dl-cv/08.md) - + [九、视频分类](docs/dl-cv/09.md) - + [十、部署](docs/dl-cv/10.md) -+ [深度学习快速参考](docs/dl-quick-ref/README.md) - + [零、前言](docs/dl-quick-ref/00.md) - + [一、深度学习的基础](docs/dl-quick-ref/01.md) - + [二、使用深度学习解决回归问题](docs/dl-quick-ref/02.md) - + [三、使用 TensorBoard 监控网络训练](docs/dl-quick-ref/03.md) - + [四、使用深度学习解决二分类问题](docs/dl-quick-ref/04.md) - + [五、使用 Keras 解决多分类问题](docs/dl-quick-ref/05.md) - + [六、超参数优化](docs/dl-quick-ref/06.md) - + [七、从头开始训练 CNN](docs/dl-quick-ref/07.md) - + [八、将预训练的 CNN 用于迁移学习](docs/dl-quick-ref/08.md) - + [九、从头开始训练 RNN](docs/dl-quick-ref/09.md) - + [十、使用词嵌入从头开始训练 LSTM](docs/dl-quick-ref/10.md) - + [十一、训练 Seq2Seq 模型](docs/dl-quick-ref/11.md) - + [十二、深度强化学习](docs/dl-quick-ref/12.md) - + [十三、生成对抗网络](docs/dl-quick-ref/13.md) -+ [TensorFlow 2.0 快速入门指南](docs/tf-20-quick-start-guide/README.md) - + [零、前言](docs/tf-20-quick-start-guide/00.md) - + [第 1 部分:TensorFlow 2.00 Alpha 简介](docs/tf-20-quick-start-guide/s1.md) - + [一、TensorFlow 2 简介](docs/tf-20-quick-start-guide/01.md) - + [二、Keras:TensorFlow 2 的高级 API](docs/tf-20-quick-start-guide/02.md) - + [三、TensorFlow 2 和 ANN 技术](docs/tf-20-quick-start-guide/03.md) - + [第 2 部分:TensorFlow 2.00 Alpha 中的监督和无监督学习](docs/tf-20-quick-start-guide/s2.md) - + [四、TensorFlow 2 和监督机器学习](docs/tf-20-quick-start-guide/04.md) - + [五、TensorFlow 2 和无监督学习](docs/tf-20-quick-start-guide/05.md) - + [第 3 部分:TensorFlow 2.00 Alpha 的神经网络应用](docs/tf-20-quick-start-guide/s3.md) - + [六、使用 TensorFlow 2 识别图像](docs/tf-20-quick-start-guide/06.md) - + [七、TensorFlow 2 和神经风格迁移](docs/tf-20-quick-start-guide/07.md) - + [八、TensorFlow 2 和循环神经网络](docs/tf-20-quick-start-guide/08.md) - + [九、TensorFlow 估计器和 TensorFlow HUB](docs/tf-20-quick-start-guide/09.md) - + [十、从 tf1.12 转换为 tf2](docs/tf-20-quick-start-guide/10.md) -+ [TensorFlow 入门](docs/get-start-tf/README.md) - + [零、前言](docs/get-start-tf/ch00.md) - + [一、TensorFlow 基本概念](docs/get-start-tf/ch01.md) - + [二、TensorFlow 数学运算](docs/get-start-tf/ch02.md) - + [三、机器学习入门](docs/get-start-tf/ch03.md) - + [四、神经网络简介](docs/get-start-tf/ch04.md) - + [五、深度学习](docs/get-start-tf/ch05.md) - + [六、TensorFlow GPU 编程和服务](docs/get-start-tf/ch06.md) -+ [TensorFlow 卷积神经网络实用指南](docs/handson-cnn-tf/README.md) - + [零、前言](docs/handson-cnn-tf/0.md) - + [一、TensorFlow 的设置和介绍](docs/handson-cnn-tf/1.md) - + [二、深度学习和卷积神经网络](docs/handson-cnn-tf/2.md) - + [三、TensorFlow 中的图像分类](docs/handson-cnn-tf/3.md) - + [四、目标检测与分割](docs/handson-cnn-tf/4.md) - + [五、VGG,Inception,ResNet 和 MobileNets](docs/handson-cnn-tf/5.md) - + [六、自编码器,变分自编码器和生成对抗网络](docs/handson-cnn-tf/6.md) - + [七、迁移学习](docs/handson-cnn-tf/7.md) - + [八、机器学习最佳实践和故障排除](docs/handson-cnn-tf/8.md) - + [九、大规模训练](docs/handson-cnn-tf/9.md) - + [十、参考文献](docs/handson-cnn-tf/10.md) -+ [Python 人工智能中文版](docs/ai-py/README.md) - + [0 前言](docs/ai-py/00.md) - + [1 人工智能简介](docs/ai-py/01.md) - + [2 人工智能的基本用例](docs/ai-py/02.md) - + [3 机器学习管道](docs/ai-py/03.md) - + [4 特征选择和特征工程](docs/ai-py/04.md) - + [5 使用监督学习的分类和回归](docs/ai-py/05.md) - + [6 集成学习的预测分析](docs/ai-py/06.md) - + [7 通过无监督学习检测模式](docs/ai-py/07.md) - + [8 构建推荐系统](docs/ai-py/08.md) - + [9 逻辑编程](docs/ai-py/09.md) - + [10 启发式搜索技术](docs/ai-py/10.md) - + [11 遗传算法和遗传编程](docs/ai-py/11.md) - + [12 云上的人工智能](docs/ai-py/12.md) - + [13 使用人工智能构建游戏](docs/ai-py/13.md) - + [14 构建语音识别器](docs/ai-py/14.md) - + [15 自然语言处理](docs/ai-py/15.md) - + [16 聊天机器人](docs/ai-py/16.md) - + [17 序列数据和时间序列分析](docs/ai-py/17.md) - + [18 图像识别](docs/ai-py/18.md) - + [19 神经网络](docs/ai-py/19.md) - + [20 将卷积神经网络用于深度学习](docs/ai-py/20.md) - + [21 循环神经网络和其他深度学习模型](docs/ai-py/21.md) - + [22 通过强化学习创建智能体](docs/ai-py/22.md) - + [23 人工智能和大数据](docs/ai-py/23.md) -+ [Python 无监督学习实用指南](docs/handson-unsup-learn-py/README.md) - + [零、前言](docs/handson-unsup-learn-py/00.md) - + [一、无监督学习入门](docs/handson-unsup-learn-py/01.md) - + [二、聚类基础](docs/handson-unsup-learn-py/02.md) - + [三、高级聚类](docs/handson-unsup-learn-py/03.md) - + [四、实用的层次聚类](docs/handson-unsup-learn-py/04.md) - + [五、软聚类和高斯混合模型](docs/handson-unsup-learn-py/05.md) - + [六、异常检测](docs/handson-unsup-learn-py/06.md) - + [七、降维和成分分析](docs/handson-unsup-learn-py/07.md) - + [八、无监督神经网络模型](docs/handson-unsup-learn-py/08.md) - + [九、生成对抗网络和 SOM](docs/handson-unsup-learn-py/09.md) - + [十、习题](docs/handson-unsup-learn-py/10.md) -+ [生成对抗网络项目](docs/gan-proj/README.md) - + [零、前言](docs/gan-proj/0.md) - + [一、生成对抗网络简介](docs/gan-proj/1.md) - + [二、3D-GAN -- 使用 GAN 生成形状](docs/gan-proj/2.md) - + [三、使用条件 GAN 进行人脸老化](docs/gan-proj/3.md) - + [四、使用 DCGAN 生成动漫角色](docs/gan-proj/4.md) - + [五、使用 SRGAN 生成逼真的图像](docs/gan-proj/5.md) - + [六、StackGAN - 逼真的文本到图像合成](docs/gan-proj/6.md) - + [七、CycleGAN - 将绘画变成照片](docs/gan-proj/7.md) - + [八、条件 GAN - 使用条件对抗网络的图像到图像翻译](docs/gan-proj/8.md) - + [九、预测 GAN 的未来](docs/gan-proj/9.md) -+ [TensorFlow 智能移动项目](docs/intel-mobi-proj-tf/README.md) - + [零、前言](docs/intel-mobi-proj-tf/00.md) - + [一、移动 TensorFlow 入门](docs/intel-mobi-proj-tf/01.md) - + [二、通过迁移学习对图像进行分类](docs/intel-mobi-proj-tf/02.md) - + [三、检测物体及其位置](docs/intel-mobi-proj-tf/03.md) - + [四、以惊人的艺术风格变换图片](docs/intel-mobi-proj-tf/04.md) - + [五、了解简单的语音命令](docs/intel-mobi-proj-tf/05.md) - + [六、用自然语言描述图像](docs/intel-mobi-proj-tf/06.md) - + [七、使用 CNN 和 LSTM 识别绘画](docs/intel-mobi-proj-tf/07.md) - + [八、用 RNN 预测股价](docs/intel-mobi-proj-tf/08.md) - + [九、使用 GAN 生成和增强图像](docs/intel-mobi-proj-tf/09.md) - + [十、构建类似 AlphaZero 的手机游戏应用](docs/intel-mobi-proj-tf/10.md) - + [十一、在移动设备上使用 TensorFlow Lite 和 Core ML](docs/intel-mobi-proj-tf/11.md) - + [十二、在 Raspberry Pi 上开发 TensorFlow 应用](docs/intel-mobi-proj-tf/12.md) -+ [TensorFlow 和 Keras 应用开发入门](docs/begin-app-dev-tf-keras/README.md) - + [零、前言](docs/begin-app-dev-tf-keras/0.md) - + [一、神经网络和深度学习简介](docs/begin-app-dev-tf-keras/1.md) - + [二、模型架构](docs/begin-app-dev-tf-keras/2.md) - + [三、模型评估和优化](docs/begin-app-dev-tf-keras/3.md) - + [四、产品化](docs/begin-app-dev-tf-keras/4.md) -+ [TensorFlow 图像深度学习实用指南](docs/handson-dl-img-tf/README.md) - + [零、前言](docs/handson-dl-img-tf/0.md) - + [一、机器学习工具包](docs/handson-dl-img-tf/1.md) - + [二、图片数据](docs/handson-dl-img-tf/2.md) - + [三、经典神经网络](docs/handson-dl-img-tf/3.md) -+ [Python 元学习实用指南](docs/handson-meta-learn-py/README.md) - + [零、前言](docs/handson-meta-learn-py/00.md) - + [一、元学习导论](docs/handson-meta-learn-py/01.md) - + [二、使用连体网络的人脸和音频识别](docs/handson-meta-learn-py/02.md) - + [三、原型网络及其变体](docs/handson-meta-learn-py/03.md) - + [四、使用 TensorFlow 的关系和匹配网络](docs/handson-meta-learn-py/04.md) - + [五、记忆增强神经网络](docs/handson-meta-learn-py/05.md) - + [六、MAML 及其变体](docs/handson-meta-learn-py/06.md) - + [七、元 SGD 和 Reptile](docs/handson-meta-learn-py/07.md) - + [八、作为优化目标的梯度一致性](docs/handson-meta-learn-py/08.md) - + [九、最新进展和后续步骤](docs/handson-meta-learn-py/09.md) - + [十、答案](docs/handson-meta-learn-py/10.md) -+ [Python 强化学习实用指南](docs/handson-rl-py/README.md) - + [零、前言](docs/handson-rl-py/00.md) - + [一、强化学习导论](docs/handson-rl-py/01.md) - + [二、OpenAI 和 TensorFlow 入门](docs/handson-rl-py/02.md) - + [三、马尔可夫决策过程与动态规划](docs/handson-rl-py/03.md) - + [四、用于游戏的蒙特卡洛方法](docs/handson-rl-py/04.md) - + [五、时间差异学习](docs/handson-rl-py/05.md) - + [六、多臂老虎机问题](docs/handson-rl-py/06.md) - + [七、深度学习基础](docs/handson-rl-py/07.md) - + [八、深度 Q 网络和 Atari 游戏](docs/handson-rl-py/08.md) - + [九、用深度循环 Q 网络玩《毁灭战士》](docs/handson-rl-py/09.md) - + [十、异步优势演员评论家网络](docs/handson-rl-py/10.md) - + [十一、策略梯度和优化](docs/handson-rl-py/11.md) - + [十二、Capstone 项目 – 将 DQN 用于赛车](docs/handson-rl-py/12.md) - + [十三、最新进展和后续步骤](docs/handson-rl-py/13.md) - + [十四、答案](docs/handson-rl-py/14.md) -+ [Python 智能项目](docs/intel-proj-py/README.md) - + [零、前言](docs/intel-proj-py/00.md) - + [一、人工智能系统的基础](docs/intel-proj-py/01.md) - + [二、迁移学习](docs/intel-proj-py/02.md) - + [三、神经机器翻译](docs/intel-proj-py/03.md) - + [四、使用 GAN 的时尚行业样式迁移](docs/intel-proj-py/04.md) - + [五、视频字幕应用](docs/intel-proj-py/05.md) - + [六、智能推荐系统](docs/intel-proj-py/06.md) - + [七、电影评论情感分析移动应用](docs/intel-proj-py/07.md) - + [八、用于客户服务的会话式 AI 聊天机器人](docs/intel-proj-py/08.md) - + [九、使用强化学习的自主无人驾驶汽车](docs/intel-proj-py/09.md) - + [十、深度学习视角的验证码](docs/intel-proj-py/10.md) -+ [精通 Sklearn 和 TensorFlow 预测性分析](docs/master-pred-anal-sklearn-tf/README.md) - + [零、前言](docs/master-pred-anal-sklearn-tf/0.md) - + [一、回归和分类的集成方法](docs/master-pred-anal-sklearn-tf/1.md) - + [二、交叉验证和参数调整](docs/master-pred-anal-sklearn-tf/2.md) - + [三、使用特征](docs/master-pred-anal-sklearn-tf/3.md) - + [四、人工神经网络和 TensorFlow 简介](docs/master-pred-anal-sklearn-tf/4.md) - + [五、将 TensorFlow 和深度神经网络用于预测分析](docs/master-pred-anal-sklearn-tf/5.md) -+ [TensorFlow 2.0 的新增功能](docs/whats-new-tf2/README.md) - + [零、前言](docs/whats-new-tf2/0.md) - + [第 1 部分:TensorFlow 2.0 - 架构和 API 更改](docs/whats-new-tf2/pt1.md) - + [一、TensorFlow 2.0 入门](docs/whats-new-tf2/1.md) - + [二、Keras 默认集成和急切执行](docs/whats-new-tf2/2.md) - + [第 2 部分:TensorFlow 2.0 - 数据和模型训练管道](docs/whats-new-tf2/pt2.md) - + [三、设计和构建输入数据管道](docs/whats-new-tf2/3.md) - + [四、TensorBoard 的模型训练和使用](docs/whats-new-tf2/4.md) - + [第 3 部分:TensorFlow 2.0 - 模型推断和部署以及 AIY](docs/whats-new-tf2/pt3.md) - + [五、模型推理管道 - 多平台部署](docs/whats-new-tf2/5.md) - + [六、AIY 项目和 TensorFlow Lite](docs/whats-new-tf2/6.md) - + [第 4 部分:TensorFlow 2.0 - 迁移,总结](docs/whats-new-tf2/pt4.md) - + [七、从 TensorFlow 1.x 迁移到 2.0](docs/whats-new-tf2/7.md) -+ [UCB CS294-112 深度强化学习中文笔记](docs/ucb-cs294-112-notes-zh/README.md) - + [(1) 简介](docs/ucb-cs294-112-notes-zh/1.md) - + [(2) 模仿学习](docs/ucb-cs294-112-notes-zh/2.md) - + [(3) 增强学习简介](docs/ucb-cs294-112-notes-zh/3.md) - + [(4) 策略梯度法](docs/ucb-cs294-112-notes-zh/4.md) - + [(5) 演员-评论家算法](docs/ucb-cs294-112-notes-zh/5.md) - + [(6) 基于值函数的方法](docs/ucb-cs294-112-notes-zh/6.md) - + [(7) 深度增强学习中的 Q 学习方法](docs/ucb-cs294-112-notes-zh/7.md) - + [(8) 最优控制与规划](docs/ucb-cs294-112-notes-zh/8.md) - + [(9) 用数据拟合模型](docs/ucb-cs294-112-notes-zh/9.md) - + [(10) 基于模型的增强学习的策略训练](docs/ucb-cs294-112-notes-zh/10.md) - + [(11) 概率图模型与软化增强学习](docs/ucb-cs294-112-notes-zh/11.md) - + [(12) 逆增强学习](docs/ucb-cs294-112-notes-zh/12.md) -+ [TensorFlow 2 和 Keras 高级深度学习](docs/adv-dl-tf2-keras/README.md) - + [零、前言](docs/adv-dl-tf2-keras/00.md) - + [一、使用 Keras 入门高级深度学习](docs/adv-dl-tf2-keras/01.md) - + [二、深度神经网络](docs/adv-dl-tf2-keras/02.md) - + [三、自编码器](docs/adv-dl-tf2-keras/03.md) - + [四、生成对抗网络(GAN)](docs/adv-dl-tf2-keras/04.md) - + [五、改进的 GAN](docs/adv-dl-tf2-keras/05.md) - + [六、纠缠表示 GAN](docs/adv-dl-tf2-keras/06.md) - + [七、跨域 GAN](docs/adv-dl-tf2-keras/07.md) - + [八、变分自编码器(VAE)](docs/adv-dl-tf2-keras/08.md) - + [九、深度强化学习](docs/adv-dl-tf2-keras/09.md) - + [十、策略梯度方法](docs/adv-dl-tf2-keras/10.md) - + [十一、对象检测](docs/adv-dl-tf2-keras/11.md) - + [十二、语义分割](docs/adv-dl-tf2-keras/12.md) - + [十三、使用互信息的无监督学习](docs/adv-dl-tf2-keras/13.md) -+ [GCP 上的人工智能实用指南](docs/handson-ai-gcp/README.md) - + [零、前言](docs/handson-ai-gcp/00.md) - + [第 1 节:Google Cloud Platform 的基础](docs/handson-ai-gcp/sec1.md) - + [一、AI 和 GCP 概述](docs/handson-ai-gcp/01.md) - + [二、使用 GCP 组件的计算和处理](docs/handson-ai-gcp/02.md) - + [第 2 节:使用 Google Cloud Platform 的人工智能](docs/handson-ai-gcp/sec2.md) - + [三、XGBoost 的机器学习应用](docs/handson-ai-gcp/03.md) - + [四、使用 Cloud AutoML](docs/handson-ai-gcp/04.md) - + [五、构建大数据云机器学习引擎](docs/handson-ai-gcp/05.md) - + [六、使用 DialogFlow 的智能对话应用](docs/handson-ai-gcp/06.md) - + [第 3 节:Google Cloud Platform 上的 TensorFlow](docs/handson-ai-gcp/sec3.md) - + [七、了解云 TPU](docs/handson-ai-gcp/07.md) - + [八、使用 Cloud ML Engine 实现 TensorFlow 模型](docs/handson-ai-gcp/08.md) - + [九、构建预测应用](docs/handson-ai-gcp/09.md) - + [第 4 节:构建应用和即将发布的功能](docs/handson-ai-gcp/sec4.md) - + [十、构建一个 AI 应用](docs/handson-ai-gcp/10.md) -+ [Python 深度学习架构实用指南](docs/handson-dl-arch-py/README.md) - + [零、前言](docs/handson-dl-arch-py/0.md) - + [第 1 节:深度学习的元素](docs/handson-dl-arch-py/sec1.md) - + [一、深度学习入门](docs/handson-dl-arch-py/1.md) - + [二、深度前馈网络](docs/handson-dl-arch-py/2.md) - + [三、受限玻尔兹曼机和自编码器](docs/handson-dl-arch-py/3.md) - + [第 2 节:卷积神经网络](docs/handson-dl-arch-py/sec2.md) - + [四、CNN 架构](docs/handson-dl-arch-py/4.md) - + [五、移动神经网络和 CNN](docs/handson-dl-arch-py/5.md) - + [第 3 节:序列建模](docs/handson-dl-arch-py/sec3.md) - + [六、循环神经网络](docs/handson-dl-arch-py/6.md) - + [第 4 节:生成对抗网络(GAN)](docs/handson-dl-arch-py/sec4.md) - + [七、生成对抗网络](docs/handson-dl-arch-py/7.md) - + [第 5 节:深度学习和高级人工智能的未来](docs/handson-dl-arch-py/sec5.md) - + [八、深度学习的新趋势](docs/handson-dl-arch-py/8.md) -+ [Python Web 深度学习实用指南](docs/handson-py-dl-web/README.md) - + [零、前言](docs/handson-py-dl-web/00.md) - + [第 1 节:Web 人工智能](docs/handson-py-dl-web/sec1.md) - + [一、揭秘人工智能和机器学习基础](docs/handson-py-dl-web/01.md) - + [第 2 节:使用深度学习的 Web 开发](docs/handson-py-dl-web/sec2.md) - + [二、使用 Python 入门深度学习](docs/handson-py-dl-web/02.md) - + [三、创建您的第一个深度学习 Web 应用](docs/handson-py-dl-web/03.md) - + [四、TensorFlow.js 入门](docs/handson-py-dl-web/04.md) - + [第 3 节:用于 Web 开发的不同深度学习 API 入门](docs/handson-py-dl-web/sec3.md) - + [五、通过 API 进行深度学习](docs/handson-py-dl-web/05.md) - + [六、Google Cloud Platform 上的 Python 深度学习](docs/handson-py-dl-web/06.md) - + [七、AWS 上的 Python DL:对象检测和家庭自动化](docs/handson-py-dl-web/07.md) - + [八、Microsoft Azure 上的 Python 深度学习](docs/handson-py-dl-web/08.md) - + [第 4 节:生产中的深度学习(智能 Web 应用)](docs/handson-py-dl-web/sec4.md) - + [九、启用深度学习的网站的通用生产框架](docs/handson-py-dl-web/09.md) - + [十、通过深度学习保护 Web 应用安全](docs/handson-py-dl-web/10.md) - + [十一、DIY - Web DL 生产环境](docs/handson-py-dl-web/11.md) - + [十二、使用 DL API 和客户支持聊天机器人创建 E2E Web 应用](docs/handson-py-dl-web/12.md) - + [十三、附录:Web 深度学习的成功案例和新兴领域](docs/handson-py-dl-web/13.md) -+ [精通 TensorFlow 2.x 计算机视觉](docs/master-cv-tf-2x/README.md) - + [零、前言](docs/master-cv-tf-2x/0.md) - + [第 1 节:计算机视觉和神经网络概论](docs/master-cv-tf-2x/sec1.md) - + [一、计算机视觉和 TensorFlow 基础知识](docs/master-cv-tf-2x/1.md) - + [二、使用局部二进制模式的内容识别](docs/master-cv-tf-2x/2.md) - + [三、使用 OpenCV 和 CNN 的人脸检测](docs/master-cv-tf-2x/3.md) - + [四、用于图像的深度学习](docs/master-cv-tf-2x/4.md) - + [第 2 节:使用 TensorFlow 的计算机视觉高级概念](docs/master-cv-tf-2x/sec2.md) - + [五、神经网络架构和模型](docs/master-cv-tf-2x/5.md) - + [六、使用迁移学习的视觉搜索](docs/master-cv-tf-2x/6.md) - + [七、YOLO 对象检测](docs/master-cv-tf-2x/7.md) - + [八、语义分割与神经样式迁移](docs/master-cv-tf-2x/8.md) - + [第 3 节:使用 TensorFlow 的计算机视觉的高级实现](docs/master-cv-tf-2x/sec3.md) - + [九、使用多任务深度学习的动作识别](docs/master-cv-tf-2x/9.md) - + [十、R-CNN,SSD 和 R-FCN 对象检测](docs/master-cv-tf-2x/10.md) - + [第 4 节:边缘和云端的 TensorFlow 实现](docs/master-cv-tf-2x/sec4.md) - + [十一、带有 CPU/GPU 优化的边缘设备上的深度学习](docs/master-cv-tf-2x/11.md) - + [十二、用于计算机视觉的云计算平台](docs/master-cv-tf-2x/12.md) -+ [TensorFlow Lite,ML Kit 和 Flutter 移动深度学习](docs/mobi-dl-tflite/README.md) - + [零、前言](docs/mobi-dl-tflite/00.md) - + [一、移动深度学习简介](docs/mobi-dl-tflite/01.md) - + [二、移动视觉 - 使用设备上的模型的人脸检测](docs/mobi-dl-tflite/02.md) - + [三、使用 Google Action 的聊天机器人](docs/mobi-dl-tflite/03.md) - + [四、认识植物种类](docs/mobi-dl-tflite/04.md) - + [五、从摄像机源生成实时字幕](docs/mobi-dl-tflite/05.md) - + [六、构建人工智能认证系统](docs/mobi-dl-tflite/06.md) - + [七、语音/多媒体处理 - 使用 AI 生成音乐](docs/mobi-dl-tflite/07.md) - + [八、基于强化神经网络的国际象棋引擎](docs/mobi-dl-tflite/08.md) - + [九、构建图像超分辨率应用](docs/mobi-dl-tflite/09.md) - + [十、前方的路](docs/mobi-dl-tflite/10.md) - + [十一、附录](docs/mobi-dl-tflite/11.md) -+ [PyTorch 人工智能研讨会](docs/dl-pt-workshop/README.md) - + [零、前言](docs/dl-pt-workshop/0.md) - + [一、深度学习和 PyTorch 简介](docs/dl-pt-workshop/1.md) - + [二、神经网络的构建块](docs/dl-pt-workshop/2.md) - + [三、使用 DNN 的分类问题](docs/dl-pt-workshop/3.md) - + [四、卷积神经网络](docs/dl-pt-workshop/4.md) - + [五、样式迁移](docs/dl-pt-workshop/5.md) - + [六、使用 RNN 分析数据序列](docs/dl-pt-workshop/6.md) - + [七、附录](docs/dl-pt-workshop/7.md) -+ [Python 一次学习实用指南](docs/handson-1shot-learn-py/README.md) - + [零、前言](docs/handson-1shot-learn-py/0.md) - + [第一部分:一次学习简介](docs/handson-1shot-learn-py/sec1.md) - + [一、一次学习简介](docs/handson-1shot-learn-py/1.md) - + [第二部分:深度学习架构](docs/handson-1shot-learn-py/sec2.md) - + [二、基于指标的方法](docs/handson-1shot-learn-py/2.md) - + [三、基于模型的方法](docs/handson-1shot-learn-py/3.md) - + [四、基于优化的方法](docs/handson-1shot-learn-py/4.md) - + [第三部分:其他方法和结论](docs/handson-1shot-learn-py/sec3.md) - + [五、基于生成建模的方法](docs/handson-1shot-learn-py/5.md) - + [六、总结和其他方法](docs/handson-1shot-learn-py/6.md) -+ [Python 自然语言处理实用指南](docs/handson-nlp-pt-1x/README.md) - + [零、前言](docs/handson-nlp-pt-1x/0.md) - + [第一部分:用于 NLP 的 PyTorch 1.x 的要点](docs/handson-nlp-pt-1x/sec1.md) - + [一、机器学习和深度学习的基础](docs/handson-nlp-pt-1x/1.md) - + [二、用于 NLP 的 PyTorch 1.x 入门](docs/handson-nlp-pt-1x/2.md) - + [第二部分:自然语言处理基础](docs/handson-nlp-pt-1x/sec2.md) - + [三、NLP 和文本嵌入](docs/handson-nlp-pt-1x/3.md) - + [四、文本预处理,词干提取和词形还原](docs/handson-nlp-pt-1x/4.md) - + [第三部分:使用 PyTorch 1.x 的实际 NLP 应用](docs/handson-nlp-pt-1x/sec3.md) - + [五、循环神经网络和情感分析](docs/handson-nlp-pt-1x/5.md) - + [六、用于文本分类的卷积神经网络](docs/handson-nlp-pt-1x/6.md) - + [七、使用序列到序列神经网络的文本翻译](docs/handson-nlp-pt-1x/7.md) - + [八、使用基于注意力的神经网络构建聊天机器人](docs/handson-nlp-pt-1x/8.md) - + [九、前方的路](docs/handson-nlp-pt-1x/9.md) -+ [PyTorch 人工智能基础知识](docs/pt-ai-fund/README.md) - + [零、前言](docs/pt-ai-fund/0.md) - + [一、使用 PyTorch 使用张量](docs/pt-ai-fund/1.md) - + [二、与神经网络协作](docs/pt-ai-fund/2.md) - + [三、用于计算机视觉的卷积神经网络](docs/pt-ai-fund/3.md) - + [四、用于 NLP 的循环神经网络](docs/pt-ai-fund/4.md) - + [五、迁移学习和 TensorBoard](docs/pt-ai-fund/5.md) - + [六、探索生成对抗网络](docs/pt-ai-fund/6.md) - + [七、深度强化学习](docs/pt-ai-fund/7.md) - + [八、在 PyTorch 中生产 AI 模型](docs/pt-ai-fund/8.md) -+ [PyTorch 深度学习实用指南](docs/pt-dl-handson/README.md) - + [零、前言](docs/pt-dl-handson/0.md) - + [一、深度学习演练和 PyTorch 简介](docs/pt-dl-handson/1.md) - + [二、简单的神经网络](docs/pt-dl-handson/2.md) - + [三、深度学习工作流程](docs/pt-dl-handson/3.md) - + [四、计算机视觉](docs/pt-dl-handson/4.md) - + [五、序列数据处理](docs/pt-dl-handson/5.md) - + [六、生成网络](docs/pt-dl-handson/6.md) - + [七、强化学习](docs/pt-dl-handson/7.md) - + [八、生产中的 PyTorch ](docs/pt-dl-handson/8.md) -+ [TensorFlow 强化学习](docs/rl-tf/README.md) - + [零、前言](docs/rl-tf/00.md) - + [一、深度学习–架构和框架](docs/rl-tf/01.md) - + [二、使用 OpenAI Gym 训练强化学习智能体](docs/rl-tf/02.md) - + [三、马尔可夫决策过程](docs/rl-tf/03.md) - + [四、策略梯度](docs/rl-tf/04.md) - + [五、Q 学习和深度 Q 网络](docs/rl-tf/05.md) - + [六、异步方法](docs/rl-tf/06.md) - + [七、一切都是机器人-真正的战略游戏](docs/rl-tf/07.md) - + [八、AlphaGo –最好的强化学习](docs/rl-tf/08.md) - + [九、自动驾驶中的强化学习](docs/rl-tf/09.md) - + [十、金融投资组合管理](docs/rl-tf/10.md) - + [十一、机器人技术中的强化学习](docs/rl-tf/11.md) - + [十二、广告技术中的深度强化学习](docs/rl-tf/12.md) - + [十三、图像处理中的强化学习](docs/rl-tf/13.md) - + [十四、NLP 中的深度强化学习](docs/rl-tf/14.md) - + [十五、强化学习的其他主题](docs/rl-tf/15.md) diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/00.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/00.md deleted file mode 100644 index 9d3c83b6..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/00.md +++ /dev/null @@ -1,137 +0,0 @@ -# 零、前言 - -近年来,深度学习在视觉,语音,自然语言处理和理解以及所有其他领域的大量数据难题中取得了前所未有的成功案例。 公司,大学,政府和研究组织对该领域的兴趣加速了该领域的发展。 本书通过三个新的章节介绍了深度学习中的重要主题:“对象检测”,“语义分割”和“使用互信息的无监督学习”。 通过提供原理的背景知识,挖掘概念背后的直觉,使用 Keras 实现方程式和算法以及检查结果来解释高级理论。 - -**人工智能**(**AI**)到今天为止还远远不是一个易于理解的领域。 **深度学习**(**DL**)作为 AI 的子字段,处于相同位置。 尽管它还不是一个成熟的领域,但许多现实世界的应用,例如基于视觉的检测和识别,自主导航,产品推荐,语音识别和合成,节能,药物发现,财务和营销,已经在使用 DL 算法。 。 将发现并构建更多应用。 本书的目的是解释高级概念,提供示例实现,并让作为其领域专家的读者识别目标应用。 - -尚未完全成熟的领域是一把双刃剑。 一方面,它为发现和利用提供了很多机会。 深度学习中有许多未解决的问题。 这就意味着可以抢先进入市场的机会–无论是在产品开发,发布还是认可方面。 另一个优势是,在关键任务环境中很难信任一个尚未被完全理解的领域。 我们可以肯定地说,如果被问到,很少有机器学习工程师会乘坐由深度学习系统控制的自动驾驶飞机。 要获得这种信任级别,需要做很多工作。 本书中讨论的高级概念很有可能在获得这种信任级别中扮演重要角色。 - -没有 DL 书能够完全涵盖整个领域。 这本书也不例外。 给定时间和空间,我们可能会涉及到有趣的领域,例如自然语言处理和理解,语音合成,自动机器学习(AutoML),图神经网络(GNN),贝叶斯深度学习等等。 但是,本书相信选择和解释选定的区域,以便读者可以从事其他未涵盖的领域。 - -作为即将着手阅读本书的读者,请记住,您选择的是一个令人兴奋的领域,会对社会产生巨大影响。 我们很幸运能有一份工作,希望我们在早晨醒来时继续努力。 - -# 这本书是给谁的 - -本书面向希望更好地了解深度学习高级主题的机器学习工程师和学生。 每个讨论都通过 Keras 中的代码实现进行了补充。 特别是,使用的是 TensorFlow 2 的 Keras API 或简称为`tf.keras`。这本书适合希望了解如何将理论转化为 Keras 中的工作代码实现的读者。 除了理解理论外,代码实现通常是将机器学习应用于实际问题的艰巨任务之一。 - -# 本书涵盖的内容 - -“第 1 章”,“Keras 高级深度学习入门”涵盖了深度学习的关键概念,例如优化,正则化,损失函数,基本层和网络及其在`tf.keras`中的实现 。 本章回顾了使用顺序 API 的深度学习和`tf.keras`。 - -“第 2 章”,“深度神经网络”讨论了`tf.keras`的函数式 API。 使用函数式 API 在`tf.keras`中检查并实现了两种广泛使用的深度网络架构 ResNet 和 DenseNet。 - -“第 3 章”,“自编码器”涵盖了一种称为自编码器的通用网络结构,该结构用于发现输入数据的潜在表示形式。 `tf.keras`中讨论并实现了自编码器的两个示例应用,即降噪和着色。 - -“第 4 章”,“生成对抗网络(GANs)”讨论了深度学习的最新重大进展之一。 GAN 用于生成看起来真实的新综合数据。 本章介绍 GAN 的原理。 在`tf.keras`中检查并实现了 GAN 的两个示例 DCGAN 和 CGAN。 - -“第 5 章”,“改进的 GAN” 涵盖了改进基本 GAN 的算法。 该算法解决了训练 GAN 的困难,并提高了合成数据的感知质量。 在`tf.keras`中讨论并实现了 WGAN,LSGAN 和 ACGAN。 - -“第 6 章”,“纠缠表示 GAN” 讨论了如何控制 GAN 生成的合成数据的属性。 如果潜在表示被解开,则可以控制属性。 `tf.keras`中介绍了并实现了两种解开表示的技术,即 InfoGAN 和 StackedGAN。 - -“第 7 章”,“跨域 GAN” 涵盖了 GAN 的实际应用,将图像从一个域转换为另一个域,通常称为跨域迁移。 CycleGAN 是一种广泛使用的跨域 GAN,在`tf.keras`中进行了讨论和实现。 本章演示 CycleGAN 执行着色和样式迁移。 - -“第 8 章”,“变分自编码器(VAE)”讨论了 DL 中的另一个重要主题。 与 GAN 类似,VAE 是用于生成综合数据的生成模型。 与 GAN 不同,VAE 专注于可解码的连续潜空间,该空间适合于变化推理。 `tf.keras`涵盖并实现了 VAE 及其变体 CVAE 和 β-VAE。 - -“第 9 章”,“深度强化学习”解释了强化学习和 Q 学习的原理。 提出了两种实现离散动作空间 Q 学习的技术,即 Q 表更新和**深度 Q 网络**(**DQN**)。 在 OpenAI Gym 环境中演示了在`tf.keras`中使用 Python 和 DQN 进行 Q 学习的实现。 - -“第 10 章”,“策略梯度方法”解释了如何使用神经网络来学习强化学习中的决策策略。 在`tf.keras`和 OpenAI Gym 环境中涵盖并实现了四种方法,即 REINFORCE,带有基线的 REINFORCE,演员评论家和优势演员评论家。 本章中的示例演示了连续操作空间上的策略梯度方法。 - -“第 11 章”,“对象检测”讨论了计算机视觉,对象检测或识别和定位图像中对象的最常见应用之一。 涵盖了称为 SSD 的多尺度目标检测算法的关键概念,并使用`tf.keras`逐步构建了实现。 提出了用于数据集收集和标记的示例技术。 之后,使用数据集对 SSD 的`tf.keras`实现进行训练和评估。 - -“第 12 章”,“语义分割”讨论了计算机视觉,语义分割或识别图像中每个像素的对象类别的另一种常见应用。 讨论了分割原理。 然后,将更详细地介绍语义分割。 使用`tf.keras`构建并评估了称为 FCN 的语义分割算法的示例实现。 使用上一章中收集的相同数据集,但重新标记了语义分割。 - -“第 13 章”,“使用互信息的无监督学习”研究了如果 DL 严重依赖人类标签,它将不会继续发展。 无监督学习侧重于不需要人工标签的算法。 一种实现无监督学习的有效技术是利用**互信息**(**MI**)的概念。 通过最大化 MI,可以使用`tf.keras`实现和评估无监督的聚类/分类。 - -# 充分利用这本书 - -* **深度学习和 Python**:读者应该具有深度学习及其在 Python 中的实现的基础知识。 尽管以前使用 Keras 实现深度学习算法的经验很重要,但这不是必需的。“第 1 章”, “Keras 高级深度学习入门”概述了深度学习的概念及其在`tf.keras`中的实现。 -* **数学**:本书中的讨论假定读者熟悉大学级别的微积分,线性代数,统计和概率。 -* **GPU**:本书中的大多数`tf.keras`实现都需要 GPU。 如果没有 GPU,则由于涉及的时间(数小时至数天),因此无法执行许多代码示例。 本书中的示例尽可能多地使用合理数量的数据,以最大程度地减少高性能计算机的使用。 读者应该至少可以使用 NVIDIA GTX 1060。 -* **编辑器**:本书的示例代码是在 Ubuntu Linux 18.04 LTS 和 MacOS Catalina 中使用 vim 编辑的。 任何支持 Python 的文本编辑器都是可以接受的。 -* **TensorFlow 2**:本书中的代码示例是使用 TensorFlow 2 的 Keras API 或`tf2`编写的。 请确保正确安装了 NVIDIA GPU 驱动和`tf2`。 -* **GitHub**:我们通过示例和实验学习。 请从其 GitHub 存储库中`git pull`或`fork`这本书的代码包。 获取代码后,对其进行检查。 运行。 更改。 再次运行。 通过调整代码进行创造性的实验。 这是欣赏本章中解释的所有理论的唯一方法。 在[此书的 GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)上点击星星也受到高度赞赏。 - -## 下载示例代码文件 - -[本书的代码包托管在 GitHub 上](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -我们还从[这里](https://github.com/PacktPublishing/)提供了丰富的书籍和视频目录中的其他代码包。 去看一下! - -## 下载彩色图像 - -我们还提供本书中使用的彩色图像图像。 [您可以在此处下载](https://static.packt-cdn.com/downloads/9787838821654_ColorImages.pdf)。 - -## 使用约定 - -本书中的代码使用 Python。 更具体地说,是 Python3。例如: - -代码块设置如下: - -```py -def build_generator(inputs, image_size): - """Build a Generator Model - Stack of BN-ReLU-Conv2DTranpose to generate fake images - Output activation is sigmoid instead of tanh in [1]. - Sigmoid converges easily. - Arguments: - inputs (Layer): Input layer of the generator - the z-vector) - image_size (tensor): Target size of one side - (assuming square image) - Returns: - generator (Model): Generator Model - """ - image_resize = image_size // 4 - # network parameters - kernel_size = 5 - layer_filters = [128, 64, 32, 1] - x = Dense(image_resize * image_resize * layer_filters[0])(inputs) - x = Reshape((image_resize, image_resize, layer_filters[0]))(x) - for filters in layer_filters: - # first two convolution layers use strides = 2 - # the last two use strides = 1 - if filters > layer_filters[-2]: - strides = 2 - else: - strides = 1 - x = BatchNormalization()(x) - x = Activation('relu')(x) - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) - x = Activation('sigmoid')(x) - generator = Model(inputs, x, name='generator') - return generator -``` - -当我们希望提请您注意代码块的特定部分时,相关的行或项以粗体显示: - -```py -# generate fake images -fake_images = generator.predict([noise, fake_labels]) -# real + fake images = 1 batch of train data -x = np.concatenate((real_images, fake_images)) -# real + fake labels = 1 batch of train data labels -labels = np.concatenate((real_labels, fake_labels)) -``` - -只要有可能,都包括文档字符串。 至少,文本注释用于最小化空间使用。 - -任何命令行代码执行都编写如下: - -```py -python3 dcgan-mnist-4.2.1.py -``` - -上面的示例具有以下布局:`algorithm-dataset-chapter.section.number.py`。 命令行示例是“第 4 章”,“生成对抗网络(GANs)”第二部分和第一列表中 MNIST 数据集上的 DCGAN。 在某些情况下,未编写要执行的显式命令行,但假定是: - -```py -python3 name-of-the-file-in-listing -``` - -该代码示例的文件名包含在“列表”标题中。 本书使用“列表”标识文本中的代码示例。 - -**粗体**:表示新的术语,重要的单词或您在屏幕上看到的单词,例如在菜单或对话框中,也显示在这样的文本中。 例如:StackedGAN 具有两个附加损失函数,即**条件**和**熵**。 - -警告或重要提示如下所示。 diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/01.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/01.md deleted file mode 100644 index 458a837b..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/01.md +++ /dev/null @@ -1,934 +0,0 @@ -# 一、使用 Keras 入门高级深度学习 - -在第一章中,我们将介绍在本书中将使用的三个深度学习人工神经网络。 这些网络是 MLP,CNN 和 RNN(在第 2 节中定义和描述),它们是本书涵盖的所选高级深度学习主题的构建块,例如自回归网络(自编码器,GAN 和 VAE),深度强化学习 ,对象检测和分割以及使用互信息的无监督学习。 - -在本章中,我们将一起讨论如何使用 Keras 库实现基于 MLP,CNN 和 RNN 的模型。 更具体地说,我们将使用名为`tf.keras`的 TensorFlow Keras 库。 我们将首先探讨为什么`tf.keras`是我们的理想选择。 接下来,我们将深入研究三个深度学习网络中的实现细节。 - -本章将: - -* 确定为什么`tf.keras`库是进行高级深度学习的绝佳选择 -* 介绍 MLP,CNN 和 RNN –高级深度学习模型的核心构建模块,我们将在本书中使用它们 -* 提供有关如何使用`tf.keras`实现基于 MLP,CNN 和 RNN 的模型的示例 -* 在此过程中,开始引入重要的深度学习概念,包括优化,正则化和损失函数 - -在本章结束时,我们将使用`tf.keras`实现基本的深度学习网络。 在下一章中,我们将介绍基于这些基础的高级深度学习主题。 让我们通过讨论 Keras 及其作为深度学习库的功能来开始本章。 - -# 1\. Keras 为什么是完美的深度学习库? - -Keras [1]是一个受欢迎的深度学习库,在撰写本文时有 370,000 个开发人员在使用它-这个数字每年以大约 35% 的速度增长。 超过 800 位贡献者积极维护它。 我们将在本书中使用的一些示例已添加到 Keras GitHub 官方存储库中。 - -谷歌的 TensorFlow 是一个流行的开源深度学习库,它使用 Keras 作为其库的高级 API。 通常称为`tf.keras`。 在本书中,我们将交替使用 Keras 和`tf.keras`一词。 - -`tf.keras`作为深度学习库是一种流行的选择,因为它已高度集成到 TensorFlow 中,TensorFlow 因其可靠性而在生产部署中广为人知。 TensorFlow 还提供了各种工具,用于生产部署和维护,调试和可视化以及在嵌入式设备和浏览器上运行模型。 在技​​术行业中,Google,Netflix,Uber 和 NVIDIA 使用 Keras。 - -我们选择`tf.keras`作为本书的首选工具,因为它是致力于加速深度学习模型实现的库。 这使得 Keras 非常适合我们想要实用且动手的时候,例如,当我们探索本书中的高级深度学习概念时。 由于 Keras 旨在加速深度学习模型的开发,训练和验证,因此在有人可以最大限度地利用库之前,必须学习该领域的关键概念。 - -[本书的所有示例都可以在 GitHub 的以下链接上找到](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -在`tf.keras`库中,各层之间就像乐高积木一样相互连接,从而形成了一个干净且易于理解的模型。 模型训练非常简单,只需要数据,大量训练和监控指标即可。 - -最终结果是,与其他深度学习库(例如 PyTorch)相比,大多数深度学习模型可以用更少的代码行来实现。 通过使用 Keras,我们将通过节省代码实现时间来提高生产率,而这些时间可以用于执行更关键的任务,例如制定更好的深度学习算法。 - -同样,Keras 是快速实现深度学习模型的理想选择,就像我们将在本书中使用的那样。 使用**顺序模型 API**,只需几行代码即可构建典型模型。 但是,不要被它的简单性所误导。 - -Keras 还可以使用其函数式 API 以及用于动态图的`Model`和`Layer`类来构建更高级和复杂的模型,可以对其进行定制以满足独特的需求。 函数式 API 支持构建类似图的模型,层重用以及创建行为类似于 Python 函数的模型。 同时,`Model`和`Layer`类提供了用于实现罕见或实验性深度学习模型和层的框架。 - -## 安装 Keras 和 TensorFlow - -Keras 不是独立的深度学习库。 如您在“图 1.1.1”中所看到的,它建立在另一个深度学习库或后端的之上。 这可能是 Google 的 TensorFlow,MILA 的 Theano,微软的 CNTK 或 Apache MXNet。 但是,与本书的上一版不同,我们将使用 TensorFlow 2.0(`tf2`或简称为`tf`)提供的 Keras(更好地称为`tf.keras`),以利用 tf2 所提供的有用工具。 `tf.keras`也被认为是 TensorFlow 的事实上的前端,它在生产环境中表现出了公认的可靠性。 此外,在不久的将来,将不再提供 Keras 对 TensorFlow 以外的后端的支持。 - -从 Keras 迁移到`tf.keras`通常就像更改一样简单: - -```py -from keras... import ... -``` - -至 - -```py -from tensorflow.keras... import ... -``` - -本书中的代码示例全部以 **Python 3** 编写,以支持 **Python 2** 于 2020 年结束。 - -在硬件上,Keras 在 CPU,GPU 和 Google 的 TPU 上运行。 在本书中,我们将在 CPU 和 NVIDIA GPU(特别是 GTX 1060,GTX 1080Ti,RTX 2080Ti,V100 和 Quadro RTX 8000)上进行测试: - -![A screenshot of a cell phone Description automatically generated](img/B14853_01_01.png) - -图 1.1.1:Keras 是位于其他深度学习框架之上的高级库。 CPU,GPU 和 TPU 支持 Keras。 - -在继续进行本书的其余部分之前,我们需要确保正确安装了`tf2`。 有多种执行安装的方法。 一个示例是通过使用`pip3`安装`tf2`: - -```py -$ sudo pip3 install tensorflow -``` - -如果我们具有支持已正确安装驱动的 NVIDIA GPU,以及 NVIDIA CUDA 工具包和 cuDNN 深度神经网络库,则强烈建议您安装启用 GPU 的版本,因为它可以加快训练和预测的速度: - -```py -$ sudo pip3 install tensorflow-gpu -``` - -无需安装 Keras,因为它已经是`tf2`中的包。 如果您不愿意在系统范围内安装库,强烈建议使用 [Anaconda](https://www.anaconda.com/distribution/) 之类的环境。 除了具有隔离环境之外,Anaconda 发行版还安装了用于数据科学的常用第三方包,这些包对于深度学习是必不可少的。 - -本书中提供的示例将需要其他包,例如`pydot`,`pydot_ng`,`vizgraph`,`python3-tk`和`matplotlib`。 在继续本章之前,我们需要安装这些包。 - -如果安装了`tf2`及其依赖项,则以下内容不会产生任何错误: - -```py -$ python3 ->>> import tensorflow as tf ->>> print(tf.__version__) -2.0.0 ->>> from tensorflow.keras import backend as K ->>> print(K.epsilon()) -1e-07 -``` - -本书没有涵盖完整的 Keras API。 我们将仅介绍解释本书中选定的高级深度学习主题所需的材料。 有关更多信息,请查阅 Keras 官方文档,该文档在[这里](https://keras.io)或[这里](https://www.tensorflow.org/guide/keras/overview)。 - -在随后的部分中,将讨论 MLP,CNN 和 RNN 的详细信息。 这些网络将用于使用`tf.keras`构建简单的分类器。 - -# 2\. MLP,CNN 和 RNN - -我们已经提到,我们将使用三个深度学习网络,它们是: - -* **MLP**:多层感知器 -* **CNN**:卷积神经网络 -* **RNN**:循环神经网络 - -这些是我们将在本书中使用的三个网络。 稍后,您会发现它们经常结合在一起以利用每个网络的优势。 - -在本章中,我们将更详细地讨论这些构建块。 在以下各节中,将介绍 MLP 以及其他重要主题,例如损失函数,优化器和正则化器。 接下来,我们将介绍 CNN 和 RNN。 - -## MLP,CNN 和 RNN 之间的区别 - -MLP 是**全连接**(**FC**)网络。 在某些文献中,您经常会发现将该称为或深度前馈网络或前馈神经网络。 在本书中,我们将使用术语 MLP。 从已知目标应用的角度了解此网络将有助于我们深入了解高级深度学习模型设计的根本原因。 - -MLP 在简单的逻辑和线性回归问题中很常见。 但是,MLP 对于处理顺序和多维数据模式不是最佳的。 通过设计,MLP 难以记住顺序数据中的模式,并且需要大量参数来处理多维数据。 - -对于顺序数据输入,RNN 很受欢迎,因为内部设计允许网络发现数据历史记录中的依存关系,这对预测很有用。 对于诸如图像和视频之类的多维数据,CNN 擅长提取用于分类,分割,生成和其他下游任务的特征映射。 在某些情况下,一维卷积形式的 CNN 也用于具有顺序输入数据的网络。 但是,在大多数深度学习模型中,将 MLP 和 CNN 或 RNN 结合起来可以充分利用每个网络。 - -MLP,CNN 和 RNN 并不完整整个深度网络。 需要识别**目标**或**损失函数**,**优化器**,和**调节器**。 目标是减少训练期间的损失函数值,因为这样的减少是模型正在学习的一个很好的指标。 - -为了使值最小化,模型使用了优化器。 这是一种算法,它确定在每个训练步骤中应如何调整权重和偏差。 经过训练的模型不仅必须对训练数据起作用,而且还必须对训练环境之外的数据起作用。 正则化器的作用是确保训练后的模型能够推广到新数据。 - -现在,让我们进入这三个网络–我们将从谈论 MLP 网络开始。 - -# 3\. 多层感知器(MLP) - -我们将要看的这三个网络中的第一个是 MLP 网络。 让我们假设目标是创建一个神经网络,用于基于手写数字识别数字。 例如,当网络的输入是手写数字 8 的图像时,相应的预测也必须是数字 8。这是分类器网络的经典工作,可以使用逻辑回归进行训练。 为了训练和验证分类器网络,必须有足够大的手写数字数据集。 *国家标准技术混合研究院*数据集,简称 MNIST [2],通常被视为 **Hello World 深度学习数据集**。 它是用于手写数字分类的合适数据集。 - -在我们讨论 MLP 分类器模型之前,必须了解 MNIST 数据集。 本书中的大量示例都使用 MNIST 数据集。 MNIST 用于来解释并验证许多深度学习理论,因为它包含的 70,000 个样本很小,但是的信息足够丰富: - -![](img/B14853_01_02.png) - -图 1.3.1:来自 MNIST 数据集的示例图像。 每个灰度图像为`28×28`像素。 - -在下面的中,我们将简要介绍 MNIST。 - -## MNIST 数据集 - -MNIST 是从 0 到 9 的手写数字的集合。它具有 60,000 张图像的训练集和 10,000 张测试图像,这些图像被分为相应的类别或标签。 在某些文献中,术语**目标**或**基本事实**也用于指**标签**。 - -在上图中,可以看到 MNIST 数字的样本图像,每个样本的大小为`28 x 28`像素(灰度)。 为了在 Keras 中使用 MNIST 数据集,提供了一个 API,用于下载并自动提取图像和标签。“列表 1.3.1”演示了如何仅在一行中加载 MNIST 数据集,从而使我们既可以计算训练和测试标签,又可以绘制 25 个随机数字图像。 - -“列表 1.3.1”:`mnist-sampler-1.3.1.py` - -```py -import numpy as np -from tensorflow.keras.datasets import mnist -import matplotlib.pyplot as plt -``` - -```py -# load dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -# count the number of unique train labels -unique, counts = np.unique(y_train, return_counts=True) -print("Train labels: ", dict(zip(unique, counts))) -``` - -```py -# count the number of unique test labels -unique, counts = np.unique(y_test, return_counts=True) -print("Test labels: ", dict(zip(unique, counts))) -``` - -```py -# sample 25 mnist digits from train dataset -indexes = np.random.randint(0, x_train.shape[0], size=25) -images = x_train[indexes] -labels = y_train[indexes] -``` - -```py -# plot the 25 mnist digits -plt.figure(figsize=(5,5)) -for i in range(len(indexes)): - plt.subplot(5, 5, i + 1) - image = images[i] - plt.imshow(image, cmap='gray') - plt.axis('off') -``` - -```py -plt.savefig("mnist-samples.png") -plt.show() -plt.close('all') -``` - -`mnist.load_data()`方法很方便,因为不需要分别加载所有 70,000 张图像和标签并将它们存储在数组中。 执行以下命令: - -```py -python3 mnist-sampler-1.3.1.py -``` - -在命令行上,该代码示例打印训练和测试数据集中的标签分布: - -```py -Train labels:{0: 5923, 1: 6742, 2: 5958, 3: 6131, 4: 5842, 5: 5421, 6: 5918, 7: 6265, 8: 5851, 9: 5949} -Test labels:{0: 980, 1: 1135, 2: 1032, 3: 1010, 4: 982, 5: 892, 6: 958, 7: 1028, 8: 974, 9: 1009} -``` - -之后,代码将绘制 25 个随机数字,如先前在“图 1.3.1”中所示。 - -在讨论 MLP 分类器模型之前,必须记住,虽然 MNIST 数据由二维张量组成,但应根据输入层的类型对它进行重塑。 以下“图 1.3.2”显示了如何为 MLP,CNN 和 RNN 输入层重塑`3×3`灰度图像: - -![](img/B14853_01_03.png) - -图 1.3.2:根据输入层的类型,对与 MNIST 数据相似的输入图像进行重塑。 为简单起见,显示了`3×3`灰度图像的重塑。 - -在以下各节中,将介绍 MNIST 的 MLP 分类器模型。 我们将演示如何使用`tf.keras`有效地构建,训练和验证模型。 - -## MNIST 数字分类器模型 - -“图 1.3.3”中显示的建议的 MLP 模型可用于 MNIST 数字分类。 当单元或感知器暴露在外时,MLP 模型是一个全连接网络,如图“图 1.3.4”所示。 我们还将展示如何根据第`n`个单元的权重`w[i]`和偏置`b[n]`的输入来计算感知器的输出。 相应的`tf.keras`实现在“列表 1.3.2”中进行了说明: - -![](img/B14853_01_04.png) - -图 1.3.3:MLP MNIST 数字分类器模型 - -![](img/B14853_01_05.png) - -图 1.3.4:图 1.3.3 中的 MLP MNIST 数字分类器由全连接层组成。 为简单起见,未显示激活层和退出层。 还详细显示了一个单元或感知器。 - -“列表 1.3.2”:`mlp-mnist-1.3.2.py` - -```py -import numpy as np -from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Dense, Activation, Dropout -from tensorflow.keras.utils import to_categorical, plot_model -from tensorflow.keras.datasets import mnist -``` - -```py -# load mnist dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -# compute the number of labels -num_labels = len(np.unique(y_train)) -``` - -```py -# convert to one-hot vector -y_train = to_categorical(y_train) -y_test = to_categorical(y_test) - -# image dimensions (assumed square) -image_size = x_train.shape[1] -input_size = image_size * image_size -``` - -```py -# resize and normalize -x_train = np.reshape(x_train, [-1, input_size]) -x_train = x_train.astype('float32') / 255 -x_test = np.reshape(x_test, [-1, input_size]) -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -batch_size = 128 -hidden_units = 256 -dropout = 0.45 -``` - -```py -# model is a 3-layer MLP with ReLU and dropout after each layer -model = Sequential() -model.add(Dense(hidden_units, input_dim=input_size)) -model.add(Activation('relu')) -model.add(Dropout(dropout)) -model.add(Dense(hidden_units)) -model.add(Activation('relu')) -model.add(Dropout(dropout)) -model.add(Dense(num_labels)) -# this is the output for one-hot vector -model.add(Activation('softmax')) -model.summary() -plot_model(model, to_file='mlp-mnist.png', show_shapes=True) -``` - -```py -# loss function for one-hot vector -# use of adam optimizer -# accuracy is good metric for classification tasks -model.compile(loss='categorical_crossentropy', - optimizer='adam', - metrics=['accuracy']) -# train the network -model.fit(x_train, y_train, epochs=20, batch_size=batch_size) -``` - -```py -# validate the model on test dataset to determine generalization -_, acc = model.evaluate(x_test, - y_test, - batch_size=batch_size, - verbose=0) -print("\nTest accuracy: %.1f%%" % (100.0 * acc)) -``` - -在讨论模型实现之前,数据必须具有正确的形状和格式。 加载 MNIST 数据集后,标签的数量计算为: - -```py -# compute the number of labels -num_labels = len(np.unique(y_train)) -``` - -硬编码`num_labels = 10`也可以选择。 但是,让计算机完成工作始终是一个好习惯。 该代码假定`y_train`的标签为 0 到 9。 - -此时,标签为数字格式,即从 0 到 9。标签的这种稀疏标量表示形式不适用于按类别输出概率的神经网络预测层。 一种更合适的格式称为`one-hot vector`,这是一个十维向量,除数字类的索引外,所有元素均为 0。 例如,如果标签为 2,则等效`one-hot vector`为[0,0,1,0,0,0,0,0,0,0]。 第一个标签的索引为 0。 - -以下各行将每个标签转换为`one-hot vector`: - -```py -# convert to one-hot vector -y_train = to_categorical(y_train) -y_test = to_categorical(y_test) -``` - -在深度学习中,数据存储在张量中。 张量一词适用于标量(0D 张量),向量(1D 张量),矩阵(二维张量)和多维张量。 - -从这一点出发,除非标量,向量或矩阵使解释更清楚,否则将使用术语张量。 - -如下所示的其余代码将计算图像尺寸,第一密集层的`input_size`值,并将每个像素值从 0 缩放到 255,范围从 0.0 缩放到 1.0。 尽管可以直接使用原始像素值,但最好对输入数据进行规范化,以避免产生可能会使训练变得困难的较大梯度值。 网络的输出也被标准化。 训练后,可以通过将输出张量乘以 255 来将所有内容恢复为整数像素值。 - -提出的模型基于 MLP 层。 因此,输入应为一维张量。 这样,将`x_train`和`x_test`分别重塑为`[60,000,28 * 28]`和`[10,000,28 * 28]`。 在 NumPy 中,大小为 -1 表示让库计算正确的尺寸。 在`x_train`的情况下为 60,000。 - -```py -# image dimensions (assumed square) 400 -image_size = x_train.shape[1] -input_size = image_size * image_size -``` - -```py -# resize and normalize -x_train = np.reshape(x_train, [-1, input_size]) -x_train = x_train.astype('float32') / 255 -x_test = np.reshape(x_test, [-1, input_size]) -x_test = x_test.astype('float32') / 255 -``` - -在准备好数据集之后,以下内容将重点介绍使用 Keras 的顺序 API 构建 MLP 分类器模型。 - -## 使用 MLP 和 Keras 构建模型 - -数据准备之后,接下来是构建模型。 所提出的模型由三个 MLP 层组成。 在 Keras 中,将 MLP 层称为**密集**,它表示紧密连接的层。 第一和第二个 MLP 层本质上是相同的,每个都有 256 个单元,然后是**整流线性单元**(**ReLU**)激活和退出。 由于 128、512 和 1,024 个单元的表现指标较低,因此选择 256 个单元。 在 128 个单元的情况下,网络收敛迅速,但测试精度较低。 512 或 1,024 的额外单元数量不会显着提高测试精度。 - -单元数是超参数。 它控制网络的**容量**。 容量是网络可以近似的函数复杂性的度量。 例如,对于多项式,度是超参数。 随着程度的增加,函数的能力也随之增加。 - -如以下代码行所示,使用 Keras 的顺序 API 实现分类器模型。 如果模型需要一个输入和一个输出(由一系列层处理),这就足够了。 为了简单起见,我们现在将使用它。 但是,在“第 2 章”,“深度神经网络”中,将引入 Keras 的函数式 API 来实现高级深度学习模型,该模型需要更复杂的结构(例如多个输入和输出)。 - -```py -# model is a 3-layer MLP with ReLU and dropout after each layer model = Sequential() -model.add(Dense(hidden_units, input_dim=input_size)) -model.add(Activation('relu')) -model.add(Dropout(dropout)) -model.add(Dense(hidden_units)) -model.add(Activation('relu')) -model.add(Dropout(dropout)) -model.add(Dense(num_labels)) -# this is the output for one-hot vector model.add(Activation('softmax')) -``` - -由于`Dense`层是线性运算,因此`Dense`层的序列只能近似线性函数。 问题是 MNIST 数字分类本质上是非线性过程。 在`Dense`层之间插入`relu`激活将使 MLP 网络能够对非线性映射建模。 `relu`或 ReLU 是一个简单的非线性函数。 这很像一个过滤器,它允许正输入不变地通过,同时将其他所有值都钳位为零。 数学上,`relu`用以下公式表示,见“图 1.3.5”: - -![](img/B14853_01_001.png) - -![](img/B14853_01_06.png) - -图 1.3.5:ReLU 函数图。 ReLU 函数在神经网络中引入了非线性。 - -还可以使用其他非线性函数,例如`elu`,`selu`,`softplus`,`sigmoid`和`tanh`。 但是,`relu`是最常用的函数,由于其简单性,在计算上是有效的。 Sigmoid 和 tanh 函数在输出层中用作激活函数,稍后将描述。“表 1.3.1”显示了每个激活函数的方程式: - -| `relu` | `relu(x) = max(0, x)` | 1.3.1 | -| --- | --- | --- | -| `softplus` | `softplus(x) = log(1 + exp(x))` | 1.3.2 | -| `elu` | ![](img/B14853_01_002.png) 其中`a≥0`并且是可调超参数 | 1.3.3 | -| `selu` | `selu(x) = k×elu(x, a)`其中`k = 1.0507009873554804934193193349852946`和`a = 1.6732632423543772848170429916717` | 1.3.4 | -| `sigmoid` | ![](img/B14853_01_003.png) | 1.3.5 | -| `tanh` | ![](img/B14853_01_004.png) | 1.3.6 | - -表 1.3.1:常见非线性激活函数的定义 - -尽管我们已完成 MLP 分类器模型的关键层,但我们尚未解决泛化问题或模型超出训练数据集的能力。 为了解决这个问题,我们将在下一节介绍正则化。 - -## 正则化 - -神经网络倾向于记住其训练数据,特别是如果它包含的容量超过。 在这种情况下,当经受测试数据时,网络将发生灾难性的故障。 这是网络无法推广的经典情况。 为了避免这种趋势,模型使用了正则化层或函数。 常见的正则化层是`Dropout`。 - -丢弃的想法很简单。 给定丢弃率(此处将其设置为`dropout = 0.45`),丢弃层会从参与下一层的单元中随机删除这一部分。 例如,如果第一层具有 256 个单元,则在应用`dropout = 0.45`之后,只有`(1-0.45) * 256`个单元,来自第 1 层的 140 个单元参与第 2 层。 - -丢弃层使神经网络对于无法预见的输入数据具有鲁棒性,因为即使缺少某些单元,训练后的神经网络也可以正确预测。 值得注意的是,输出层中没有使用丢弃,它仅在训练期间处于活动状态。 此外,在预测期间不存在丢弃现象。 - -除了诸如丢弃之类的正则化之外,还可以使用其他正则化器。 在 Keras 中,可以按层对偏置,权重和激活输出进行正则化。 `l1`和`l2`通过添加罚函数来支持较小的参数值。 `l1`和`l2`都使用绝对值(`l1`)或平方(`l2`)之和的分数来执行惩罚。 换句话说,惩罚函数迫使优化器找到较小的参数值。 参数值小的神经网络对来自输入数据的噪声的存在更加不敏感。 - -例如,带有`fraction=0.001`的`l2`权重正则器可以实现为: - -```py -from tensorflow.keras.regularizers import l2 -model.add(Dense(hidden_units, - kernel_regularizer=l2(0.001), - input_dim=input_size)) -``` - -如果使用`l1`或`l2`正则化,则不添加任何附加层。 正则化在内部施加在`Dense`层中。 对于建议的模型,丢弃仍然具有比`l2`更好的表现。 - -我们的模型几乎已经完成。 下一节将重点介绍输出层和损失函数。 - -## 输出激活和损失函数 - -输出的层具有 10 个单元,其后是`softmax`激活层。 这 10 个单元对应于 10 个可能的标签,类或类别。 可以用数学方式表示`softmax`激活,如以下等式所示: - -![](img/B14853_01_005.png) (Equation 1.3.7) - -该方程适用于所有`N = 10`输出,`x[i]`对于`i = 0, 1, ..., 9`作最终预测。 `softmax`的概念非常简单。 通过对预测进行归一化,将输出压缩为概率。 在此,每个预测输出都是该索引是给定输入图像的正确标签的概率。 所有输出的所有概率之和为 1.0。 例如,当`softmax`层生成预测时,它将是一个 10 维一维张量,看起来像以下输出: - -```py -[3.57351579e-11 7.08998016e-08 - 2.30154569e-07 6.35787558e-07 - 5.57471187e-11 4.15353840e-09 - 3.55973775e-16 9.99995947e-01 - 1.29531730e-09 3.06023480e-06] -``` - -预测输出张量建议输入图像的索引具有最高概率,因此将为 7。 `numpy.argmax()`方法可用于确定具有最高值的元素的索引。 - -输出激活层还有其他选择,例如`linear`,`sigmoid`或`tanh`。 `linear`激活是一种恒等函数。 它将其输入复制到其输出。 `sigmoid`函数更具体地是,称为**逻辑 Sigmoid**。 如果预测张量的元素将独立地映射在 0.0 和 1.0 之间,则将使用此方法。 与`softmax`中不同,预测张量的所有元素的总和不限于 1.0。 例如,`sigmoid`用作情感预测(从 0.0 到 1.0、0.0 不好,1.0 很好)或图像生成(0.0 映射到像素级别 0 和 1.0 映射到像素 255)的最后一层 。 - -`tanh`函数将其输入映射在 -1.0 到 1.0 的范围内。 如果输出可以同时以正值和负值摆幅,则这一点很重要。 `tanh`函数在循环神经网络的内部层中更普遍使用,但也已用作输出层激活。 如果在输出激活中使用 tanh 代替`sigmoid`,则必须适当缩放使用的数据。 例如,不是使用`x = x / 255`缩放`[0.0, 1.0]`范围内的每个灰度像素,而是使用`x = (x - 127.5) / 127.5`将其分配在`[-1.0, 1.0]`范围内。 - -下图“图 1.3.6”显示了`sigmoid`和`tanh`函数。 数学上,Sigmoid 可以用以下公式表示: - -![](img/B14853_01_008.png) (Equation 1.3.5) - -![](img/B14853_01_07.png) - -图 1.3.6:Sigmoid 和正切图 - -预测张量距单热地面真值向量有多远称为损失。 损失函数的一种类型是`mean_squared_error`(**MSE**),或者是目标或标签与预测之间差异的平方的平均值。 在当前示例中,我们使用`categorical_crossentropy`。 它是目标或标签乘积与每个类别的预测对数之和的负数。 Keras 中还有其他损失函数,例如`mean_absolute_error`和`binary_crossentropy`。“表 1.3.2”总结了的常见损失函数。 - -| **损失函数** | **公式** | -| --- | --- | -| `mean_squared_error` | ![](img/B14853_01_009.png) | -| `mean_absolute_error` | ![](img/B14853_01_010.png) | -| `categorical_crossentropy` | ![](img/B14853_01_011.png) | -| `binary_crossentropy` | ![](img/B14853_01_012.png) | - -表 1.3.2:常见损失函数汇总。 类别是指标签和预测中的类别数(例如:MNIST 为 10)。 所示的损失方程式仅适用于一个输出。 平均损失值是整个批量的平均值。 - -损失函数的选择不是任意的,而应作为模型正在学习的标准。 对于按类别进行分类,在`softmax`激活层之后,`categorical_crossentropy`或`mean_squared_error`是一个不错的选择。 `binary_crossentropy`损失函数通常在`sigmoid`激活层之后使用,而`mean_squared_error`是`tanh`输出的选项。 - -在下一部分中,我们将讨论优化算法以最小化我们在此处讨论的损失函数。 - -## 优化 - -通过优化,目标是使损失函数最小化。 这个想法是,如果将损失减少到可接受的水平,则该模型将间接学习将输入映射到输出的函数。 表现指标用于确定模型是否了解了基础数据分布。 Keras 中的默认指标是**损失**。 在训练,验证和测试期间,还可以包括其他指标,例如**准确率**。 准确率是基于地面真实性的正确预测的百分比或分数。 在深度学习中,还有许多其他表现指标。 但是,它取决于模型的目标应用。 在文献中,报告了**测试数据集**上训练后的模型的表现指标,用于与其他深度学习模型进行比较。 - -在 Keras 中,优化器有个选择。 最常用的优化器是**随机梯度下降**(**SGD**),**自适应矩**(**Adam**)和**均方根传播**(**RMSprop**)。 每个优化器均具有可调参数,例如学习率,动量和衰减。 Adam 和 RMSprop 是具有自适应学习率的 SGD 的变体。 在提出的分类器网络中,使用了 Adam,因为它具有最高的测试精度。 - -SGD 被认为是最基本的优化程序。 它是演算中梯度下降的简单版本。 在**梯度下降**(**GD**)中,追踪下坡函数的曲线可找到最小值,就像在山谷中下坡直至到达底部一样。 - -GD 算法如图 1.3.7 所示。 假设`x`是被调整以找到`y`的最小值(例如,损失函数)的参数(例如,权重)。 从`x = -0.5`的任意点开始。 梯度`dy/dx = -2.0`。 GD 算法强加`x`然后更新为`x = -0.5 - ε(-2.0)`。 `x`的新值等于旧值,再加上`ε`缩放的梯度的相反值。 小数字`ε`是指学习率。 如果`ε = 0.01`,则`x`的新值为 -0.48。 GD 是迭代执行的。 在每一步,`y`都将接近其最小值。 在`x = 0.5`时,`dy/dx = 0`。 GD 已找到`y = -1.25`的绝对最小值。 梯度建议不要进一步改变`x`。 - -学习率的选择至关重要。 大的`ε`值可能找不到最小值,因为搜索只会在最小值附近来回摆动。 一方面,在找到最小值之前,较大的`ε`值可能需要进行大量迭代。 在有多个最小值的情况下,搜索可能会陷入局部最小值。 - -![](img/B14853_01_08.png) - -图 1.3.7:GD 类似于在函数曲线上向下走直到到达最低点。 在此图中,全局最小值为`x = 0.5`。 - -多个极小值的示例可以在“图 1.3.8”中看到。 如果由于某种原因从图的左侧开始搜索并且学习率很小,则 GD 很可能会发现`x = -1.51`是*最小值* 。 GD 无法在`x = 1.66`时找到全局最小值。 具有足够值的学习率将使 GD 可以克服`x = 0.0`的问题。 - -在深度学习实践中,通常建议从更高的学习率开始(例如,从 0.1 到 0.001),并随着损失接近最小值而逐渐降低学习率。 - -![](img/B14853_01_09.png) - -图 1.3.8:具有 2 个最小值的函数图,`x = -1.51`和`x = 1.66`。 还显示了该函数的导数。 - -GD 通常不用于深度神经网络,因为遇到数百万个要训练的参数很常见。 执行完整的 GD 在计算上效率低下。 而是使用 SGD。 在 SGD 中,选择一小批样本以计算下降的近似值。 参数(例如权重和偏差)可通过以下公式进行调整: - -![](img/B14853_01_021.png) - -在该等式中,`θ`和`g = 1/m ᐁ[θ] ΣL`分别是损失函数的参数和梯度张量。`g`由损失函数的偏导数计算得出。 出于 GPU 优化的目的,建议最小批量大小为 2 的幂。 在建议的网络中,`batch_size = 128`。 - -“公式 1.3.8”计算最后一层参数更新。 那么,我们如何调整前几层的参数呢? 在这种情况下,应用微分链规则将导数传播到较低层并相应地计算梯度。 该算法在深度学习中称为**反向传播**。 反向传播的详细信息超出了本书的范围。 但是,可以在[这里](http://neuralnetworksanddeeplearning.com)找到很好的在线参考。 - -由于优化是基于微分的,因此得出损失函数的重要标准是它必须平滑或可微。 当引入新的损失函数时,这是要牢记的重要约束。 - -给定训练数据集,损失函数的选择,优化器和正则化器,现在可以通过调用`fit()`函数来训练模型: - -```py -# loss function for one-hot vector -# use of adam optimizer -# accuracy is a good metric for classification tasks model.compile(loss='categorical_crossentropy', -optimizer='adam', metrics=['accuracy']) -``` - -```py -# train the network -model.fit(x_train, y_train, epochs=20, batch_size=batch_size) -``` - -这是 Keras 的另一个有用函数。 通过仅提供`x`和`y`数据,要训练的周期数和批量大小,`fit()`完成了其余工作。 在其他深度学习框架中,这转化为多项任务,例如以适当的格式准备输入和输出数据,加载,监视等等。 尽管所有这些都必须在`for`循环内完成,但在 Keras 中,一切都只需要一行即可完成。 - -在`fit()`函数中,一个周期是整个训练数据的完整采样。 `batch_size`参数是每个训练步骤要处理的输入数量的样本大小。 为了完成一个周期,`fit()`将处理等于训练数据集大小的步数除以批量大小再加上 1,以补偿任何小数部分。 - -训练模型后,我们现在可以评估其表现。 - -## 表现评估 - -至此,MNIST 数字分类器的模型现已完成。 表现评估将是的下一个关键步骤,以确定提议的训练模型是否已提出令人满意的解决方案。 将模型训练 20 个时间段就足以获得可比较的表现指标。 - -下表“表 1.3.3”列出了不同的网络配置和相应的表现指标。 在“层”下,显示第 1 到第 3 层的单元数。对于每个优化器,将使用`tf.keras`中的默认参数。 可以观察到改变正则化器,优化器和每层单元数的效果。“表 1.3.3”中的另一个重要观察结果是,更大的网络不一定会转化为更好的表现。 - -在训练和测试数据集的准确率方面,增加此网络的深度不会显示任何其他好处。 另一方面,较少的单元(例如 128)也可能会降低测试和训练的准确率。 删除正则器后,将在`99.93%`处获得最佳的训练精度,并且每层使用 256 个单元。 但是,由于网络过拟合,测试精度在`98.0%`时要低得多。 - -最高的测试精度是使用 Adam 优化器和`98.5%`处的`Dropout(0.45)`。 从技术上讲,鉴于其训练精度为`99.39%`,仍然存在某种程度的过拟合。 对于`256-512-256`,`Dropout(0.45)`和 SGD,在`98.2%`时,训练和测试精度均相同。 同时去除正则化和 ReLU 层会导致其表现最差。 通常,我们会发现`Dropout`层比`l2`具有更好的表现。 - -下表演示了调整期间典型的深度神经网络表现: - -| **层** | **正则化函数** | **优化器** | **ReLU** | **训练准确率(%)** | **测试准确率(%)** | -| --- | --- | --- | --- | --- | --- | -| 256-256-256 | 没有 | SGD | 没有 | 93.65 | 92.5 | -| 256-256-256 | L2(0.001) | SGD | 是 | 99.35 | 98.0 | -| 256-256-256 | L2(0.01) | SGD | 是 | 96.90 | 96.7 | -| 256-256-256 | 没有 | SGD | 是 | 99.93 | 98.0 | -| 256-256-256 | 丢弃(0.4) | SGD | 是 | 98.23 | 98.1 | -| 256-256-256 | 丢弃(0.45) | SGD | 是 | 98.07 | 98.1 | -| 256-256-256 | 丢弃(0.5) | SGD | 是 | 97.68 | 98.1 | -| 256-256-256 | 丢弃(0.6) | SGD | 是 | 97.11 | 97.9 | -| 256-512-256 | 丢弃(0.45) | SGD | 是 | 98.21 | 98.2 | -| 512-512-512 | 丢弃(0.2) | SGD | 是 | 99.45 | 98.3 | -| 512-512-512 | 丢弃(0.4) | SGD | 是 | 98.95 | 98.3 | -| 512-1024-512 | 丢弃(0.45) | SGD | 是 | 98.90 | 98.2 | -| 1024-1024-1024 | 丢弃(0.4) | SGD | 是 | 99.37 | 98.3 | -| 256-256-256 | 丢弃(0.6) | Adam | 是 | 98.64 | 98.2 | -| 256-256-256 | 丢弃(0.55) | Adam | 是 | 99.02 | 98.3 | -| 256-256-256 | 丢弃(0.45) | Adam | 是 | 99.39 | 98.5 | -| 256-256-256 | 丢弃(0.45) | RMSprop | 是 | 98.75 | 98.1 | -| 128-128-128 | 丢弃(0.45) | Adam | 是 | 98.70 | 97.7 | - -表 1.3.3 不同的 MLP 网络配置和表现指标 - -示例指示需要改进网络架构。 在下一节讨论了 MLP 分类器模型摘要之后,我们将介绍另一个 MNIST 分类器。 下一个模型基于 CNN,并证明了测试准确率的显着提高。 - -## 模型摘要 - -使用 Keras 库为我们提供了一种快速的机制,可以通过调用以下方法来仔细检查模型描述: - -```py -model.summary() -``` - -下面的“列表 1.3.3”显示了所建议网络的模型摘要。 它总共需要 269,322 个参数。 考虑到我们具有对 MNIST 数字进行分类的简单任务,这一点非常重要。 MLP 的参数效率不高。 可以通过关注如何计算感知器的输出,从“图 1.3.4”计算参数的数量。 从输入到密集层:`784 × 256 + 256 = 200,960`。 从第一密集层到第二密集层:`256 × 256 + 256 = 65,792`。 从第二个密集层到输出层:`10 × 256 + 10 = 2,570`。 总数是`269,322`。 - -“列表 1.3.3”:MLP MNIST 数字分类器模型的摘要: - -```py -Layer (type) Output Shape Param # -================================================================= -dense_1 (Dense) (None, 256) 200960 -activation_1 (Activation) (None, 256) 0 -dropout_1 (Dropout) (None, 256) 0 -dense_2 (Dense) (None, 256) 65792 -activation_2 (Activation) (None, 256) 0 -dropout_2 (Dropout) (None, 256) 0 -dense_3 (Dense) (None, 10) 2750 -activation_3 (Activation) (None, 10) 0 -================================================================= -Total params: 269,322 -Trainable params: 269,322 -Non-trainable params: 0 -``` - -验证网络的另一种方法是通过调用: - -```py -plot_model(model, to_file='mlp-mnist.png', show_shapes=True) -``` - -“图 1.3.9”显示了该图。 您会发现这类似于`summary()`的结果,但是以图形方式显示了每个层的互连和 I/O。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_01_10.png) - -图 1.3.9:MLP MNIST 数字分类器的图形描述 - -在总结了我们模型的之后,到此结束了我们对 MLP 的讨论。 在下一部分中,我们将基于 CNN 构建 MNIST 数字分类器模型。 - -# 4\. 卷积神经网络(CNN) - -现在,我们将进入第二个人工神经网络 CNN。 在本节中,我们将解决相同的 MNIST 数字分类问题,但这一次使用 CNN。 - -“图 1.4.1”显示了我们将用于 MNIST 数字分类的 CNN 模型,而其实现在“列表 1.4.1”中进行了说明。 实现 CNN 模型将需要对先前模型进行一些更改。 现在,输入张量不再具有输入向量,而具有新尺寸(`height`,`width`,`channels`)或(`image_size`,`image_size`,`1`)=(`28`,`28` ,`1`)用于 MNIST 灰度图像。 需要调整训练和测试图像的大小以符合此输入形状要求。 - -![](img/B14853_01_11.png) - -图 1.4.1:用于 MNIST 数字分类的 CNN 模型 - -实现上图: - -“列表 1.4.1”:`cnn-mnist-1.4.1.py` - -```py -import numpy as np -from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Activation, Dense, Dropout -from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten -from tensorflow.keras.utils import to_categorical, plot_model -from tensorflow.keras.datasets import mnist -``` - -```py -# load mnist dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -# compute the number of labels -num_labels = len(np.unique(y_train)) -``` - -```py -# convert to one-hot vector -y_train = to_categorical(y_train) -y_test = to_categorical(y_test) -``` - -```py -# input image dimensions -image_size = x_train.shape[1] -# resize and normalize -x_train = np.reshape(x_train,[-1, image_size, image_size, 1]) -x_test = np.reshape(x_test,[-1, image_size, image_size, 1]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -# image is processed as is (square grayscale) -input_shape = (image_size, image_size, 1) -batch_size = 128 -kernel_size = 3 -pool_size = 2 -filters = 64 -dropout = 0.2 -``` - -```py -# model is a stack of CNN-ReLU-MaxPooling -model = Sequential() -model.add(Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu', - input_shape=input_shape)) -model.add(MaxPooling2D(pool_size)) -model.add(Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu')) -model.add(MaxPooling2D(pool_size)) -model.add(Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu')) -model.add(Flatten()) -# dropout added as regularizer -model.add(Dropout(dropout)) -# output layer is 10-dim one-hot vector -model.add(Dense(num_labels)) -model.add(Activation('softmax')) -model.summary() -plot_model(model, to_file='cnn-mnist.png', show_shapes=True) -``` - -```py -# loss function for one-hot vector -# use of adam optimizer -# accuracy is good metric for classification tasks -model.compile(loss='categorical_crossentropy', - optimizer='adam', - metrics=['accuracy']) -# train the network -model.fit(x_train, y_train, epochs=10, batch_size=batch_size) -``` - -```py -_, acc = model.evaluate(x_test, - y_test, - batch_size=batch_size, - verbose=0) -print("\nTest accuracy: %.1f%%" % (100.0 * acc)) -``` - -的主要更改是`Conv2D`层的使用。 `ReLU`激活函数已经是`Conv2D`的参数。 当模型中包含`batch normalization`层时,可以将`ReLU`函数作为`Activation`层使用。 `Batch normalization`用于深层 CNN,因此可以利用较大的学习率而不会引起训练过程中的不稳定。 - -## 卷积 - -如果在 MLP 模型中,单元数量表示密集层,则核表示 CNN 操作。 如图“图 1.4.2”所示,可以将核可视化为矩形补丁或窗口,该补丁或窗口从左到右,从上到下在整个图像中滑动。 此操作称为卷积。 它将输入图像转换成特征映射,该特征映射表示核从输入图像中学到的内容。 然后将特征映射转换为后续层中的另一个特征映射,依此类推。 每个`Conv2D`生成的特征映射的数量由`filters`参数控制。 - -![](img/B14853_01_12.png) - -图 1.4.2:3×3 核与 MNIST 数字图像卷积。 - -在步骤`t[n]`和`t[n + 1]`中显示了卷积,其中核向右移动了 1 个像素 。 - -卷积中涉及的计算显示在“图 1.4.3”中: - -![](img/B14853_01_13.png) - -图 1.4.3:卷积运算显示如何计算特征映射的一个元素 - -为简单起见,显示了应用了`3×3`核的`3×3`输入图像(或输入特征映射)。 卷积后显示结果特征映射。 特征映射中一个元素的值被加阴影。 您会注意到,结果特征映射小于原始输入图像的,这是因为卷积仅在有效元素上执行。 核不能超出映像的边界。 如果输入的尺寸应与输出特征映射相同,则`Conv2D`接受选项`padding='same'`。 输入在其边界周围填充零,以在卷积后保持尺寸不变。 - -## 池化操作 - -最后的更改是添加了`MaxPooling2D`层以及参数`pool_size=2`。 `MaxPooling2D`压缩每个特征映射。 每个大小为`pool_size × pool_size`的补丁都减少为 1 个特征映射点。 该值等于补丁中的最大特征点值。 下图显示了`MaxPooling2D`的两个补丁: - -![](img/B14853_01_14.png) - -图 1.4.4:`MaxPooling2D`操作。 为简单起见,输入特征映射为`4×4`,结果为`2×2`特征映射。 - -`MaxPooling2D`的意义在于特征映射尺寸的减小,这转化为感受野尺寸的增加。 例如,在`MaxPooling2D(2)`之后,2×2 核现在大约与`4×4`补丁卷积。 CNN 学会了针对不同接收场大小的一组新的特征映射。 - -还有其他合并和压缩方式。 例如,要使`MaxPooling2D(2)`的尺寸减少 50%,`AveragePooling2D(2)`会取一个补丁的平均值而不是找到最大值。 交叉卷积`Conv2D(strides=2,…)`在卷积过程中将跳过每两个像素,并且仍具有相同的 50% 缩小效果。 每种还原技术的有效性都有细微的差异。 - -在`Conv2D`和`MaxPooling2D`中,`pool_size`和`kernel`都可以是非正方形的。 在这些情况下,必须同时指定行和列的大小。 例如,`pool_ size = (1, 2)`和`kernel = (3, 5)`。 - -最后一个`MaxPooling2D`操作的输出是一堆特征映射。 `Flatten`的作用是,将特征映射的栈转换为适用于`Dropout`或`Dense`层的向量格式,类似于 MLP 模型输出层。 - -在下一部分中,我们将评估经过训练的 MNIST CNN 分类器模型的表现。 - -## 表现评估和模型摘要 - -如“列表 1.4.2”中所示,“列表 1.4.1”中的 CNN 模型在 80,226 处需要较少数量的参数,而使用 MLP 层时需要 269,322 个参数。 `conv2d_1`层具有 640 个参数,因为每个核具有`3×3 = 9`个参数,并且 64 个特征映射中的每一个都有一个核,一个偏置参数。 其他卷积层的参数数量可以类似的方式计算。 - -“列表 1.4.2”:CNN MNIST 数字分类器的摘要 - -```py -Layer (type) Output Shape Param # -================================================================= -conv2d_1 (Conv2D) (None, 26, 26, 64) 640 -max_pooling2d_1 (MaxPooiling2) (None, 13, 13, 64) 0 -conv2d_2 (Conv2D) (None, 11, 11, 64) 36928 -max_pooling2d_2 (MaxPooiling2) (None, 5.5, 5, 64) 0 -conv2d_3 (Conv2D) (None, 3.3, 3, 64) 36928 -flatten_1 (Flatten) (None, 576) 0 -dropout_1 (Dropout) (None, 576) 0 -dense_1 (Dense) (None, 10) 5770 -activation_1 (Activation) (None, 10) 0 -=================================================================== -Total params: 80,266 -Trainable params: 80,266 -Non-trainable params: 0 -``` - -“图 1.4.5”:显示了 CNN MNIST 数字分类器的图形表示形式。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_01_15.png) - -图 1.4.5:CNN MNIST 数字分类器的图形描述 - -“表 1.4.1”显示了 99.4% 的最大测试准确率,这对于使用带有`dropout=0.2`的 Adam 优化器的每层具有 64 个特征映射的 3 层网络可以实现。 CNN 比 MLP 具有更高的参数效率,并且具有更高的准确率。 同样,CNN 也适合从顺序数据,图像和视频中学习表示形式。 - -| **层** | **优化器** | **正则化函数** | **训练准确率(%)** | **测试准确率(%)** | -| --- | --- | --- | --- | --- | --- | -| 64-64-64 | SGD | 丢弃(0.2) | 97.76 | 98.50 | -| 64-64-64 | RMSprop | 丢弃(0.2) | 99.11 | 99.00 | -| 64-64-64 | Adam | 丢弃(0.2) | 99.75 | 99.40 | -| 64-64-64 | Adam | 丢弃(0.4) | 99.64 | 99.30 | - -表 1.4.1:CNN MNIST 数字分类器的不同 CNN 网络配置和表现指标。 - -看了 CNN 并评估了训练好的模型之后,让我们看一下我们将在本章中讨论的最终核心网络:RNN。 - -# 5\. 循环神经网络(RNN) - -现在,我们来看一下三个人工神经网络中的最后一个,即 RNN。 - -RNN 是网络的序列,适用于学习顺序数据的表示形式,例如**自然语言处理**(**NLP**)中的文本或仪器中的传感器数据流 。 尽管每个 MNIST 数据样本本质上都不是顺序的,但不难想象每个图像都可以解释为像素行或列的序列。 因此,基于 RNN 的模型可以将每个 MNIST 图像作为 28 个元素的输入向量序列进行处理,时间步长等于 28。下面的清单在“图 1.5.1”中显示了 RNN 模型的代码: - -![](img/B14853_01_16.png) - -图 1.5.1:用于 MNIST 数字分类的 RNN 模型 - -“列表 1.5.1”:`rnn-mnist-1.5.1.py` - -```py -import numpy as np -from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Dense, Activation, SimpleRNN -from tensorflow.keras.utils import to_categorical, plot_model -from tensorflow.keras.datasets import mnist -``` - -```py -# load mnist dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -# compute the number of labels -num_labels = len(np.unique(y_train)) -``` - -```py -# convert to one-hot vector -y_train = to_categorical(y_train) -y_test = to_categorical(y_test) -``` - -```py -# resize and normalize -image_size = x_train.shape[1] -x_train = np.reshape(x_train,[-1, image_size, image_size]) -x_test = np.reshape(x_test,[-1, image_size, image_size]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -input_shape = (image_size, image_size) -batch_size = 128 -units = 256 -dropout = 0.2 -``` - -```py -# model is RNN with 256 units, input is 28-dim vector 28 timesteps -model = Sequential() -model.add(SimpleRNN(units=units, - dropout=dropout, - input_shape=input_shape)) -model.add(Dense(num_labels)) -model.add(Activation('softmax')) -model.summary() -plot_model(model, to_file='rnn-mnist.png', show_shapes=True) -``` - -```py -# loss function for one-hot vector -# use of sgd optimizer -# accuracy is good metric for classification tasks -model.compile(loss='categorical_crossentropy', - optimizer='sgd', - metrics=['accuracy']) -# train the network -model.fit(x_train, y_train, epochs=20, batch_size=batch_size) -``` - -```py -_, acc = model.evaluate(x_test, - y_test, - batch_size=batch_size, - verbose=0) -print("\nTest accuracy: %.1f%%" % (100.0 * acc)) -``` - -RNN 分类器与之前的两个模型之间有两个主要区别。 首先是`input_shape = (image_size, image_size)`,它实际上是`input_ shape = (timesteps, input_dim)`或时间步长的`input_dim`维向量序列。 其次是使用`SimpleRNN`层以`units=256`表示 RNN 单元。 `units`变量代表输出单元的数量。 如果 CNN 是通过输入特征映射上的核卷积来表征的,则 RNN 输出不仅是当前输入的函数,而且是先前输出或隐藏状态的函数。 由于前一个输出也是前一个输入的函数,因此当前输出也是前一个输出和输入的函数,依此类推。 Keras 中的`SimpleRNN`层是真实 RNN 的简化版本。 以下等式描述了`SimpleRNN`的输出: - -![](img/B14853_01_024.png) (Equation 1.5.1) - -在此等式中,`b`是偏差,而`W`和`U`被称为循环核(先前输出的权重)和核(当前输入的权重) ), 分别。 下标`t`用于指示序列中的位置。 对于具有`units=256`的`SimpleRNN`层,参数总数为`256 + 256×256 + 256×28 = 72,960`,对应于`b`,`W`和个贡献。 - -下图显示了用于分类任务的`SimpleRNN`和 RNN 的图。 使`SimpleRNN`比 RNN 更简单的是缺少输出值`o[t] = Vh[t] + c`在计算`softmax`函数之前: - -![](img/B14853_01_17.png) - -图 1.5.2:`SimpleRNN`和 RNN 图 - -与 MLP 或 CNN 相比,RNN 最初可能较难理解。 在 MLP 中,感知器是基本单元。 一旦了解了感知器的概念,MLP 就是感知器的网络。 在 CNN 中,核是一个补丁或窗口,可在特征映射中滑动以生成另一个特征映射。 在 RNN 中,最重要的是自环的概念。 实际上只有一个单元。 - -出现多个单元的错觉是因为每个时间步都有一个单元,但实际上,除非网络展开,否则它只是重复使用的同一单元。 RNN 的基础神经网络在单元之间共享。 - -“列表 1.5.2”中的摘要指示使用`SimpleRNN`需要较少数量的参数。 - -“列表 1.5.2”:RNN MNIST 数字分类器的摘要 - -```py -Layer (type) Output Shape Param # -================================================================= -simple_rnn_1 (SimpleRNN) (None, 256) 72960 -dense_1 (Dense) (None, 10) 2570 -activation_1 (Activation) (None, 10) 36928 -================================================================= -Total params: 75,530 -Trainable params: 75,530 -Non-trainable params: 0 -``` - -“图 1.5.3”显示了 RNN MNIST 数字分类器的图形描述。 该模型非常简洁: - -![A screenshot of a cell phone Description automatically generated](img/B14853_01_18.png) - -图 1.5.3:RNN MNIST 数字分类器图形说明 - -“表 1.5.1”显示 SimpleRNN 在所呈现的网络中具有最低的准确率: - -| **层** | **优化器** | **正则化函数** | **训练准确率(%)** | **测试准确率(%)** | -| --- | --- | --- | --- | --- | --- | -| 256 | SGD | 丢弃(0.2) | 97.26 | 98.00 | -| 256 | RMSprop | 丢弃(0.2) | 96.72 | 97.60 | -| 256 | Adam | 丢弃(0.2) | 96.79 | 97.40 | -| 512 | SGD | 丢弃(0.2) | 97.88 | 98.30 | - -表 1.5.1:不同的`SimpleRNN`网络配置和表现指标 - -在许多深度神经网络中,更常使用 RNN 家族的其他成员。 例如,机器翻译和问答问题都使用了**长短期记忆**(**LSTM**)。 LSTM 解决了长期依赖或记住与当前输出相关的过去信息的问题。 - -与 RNN 或`SimpleRNN`不同,LSTM 单元的内部结构更为复杂。“图 1.5.4”显示了 LSTM 的示意图。 LSTM 不仅使用当前输入和过去的输出或隐藏状态,还引入了一个单元状态`s[t]`,该状态将信息从一个单元传送到另一个单元。 单元状态之间的信息流由三个门控制`f[t]`,`i[t]`和`q[t]`。 这三个门的作用是确定应保留或替换哪些信息,以及过去对当前单元状态或输出有贡献的信息量以及过去和当前的输入。 我们不会在本书中讨论 LSTM 单元内部结构的细节。 但是,可以在[这个页面](http://colah.github.io/posts/2015-08-Understanding-LSTMs)上找到 LSTM 的直观指南。 - -`LSTM()`层可以用作`SimpleRNN()`的嵌入式替代。 如果 LSTM 对于手头的任务过于苛刻,则可以使用更简单的版本,称为**门控循环单元**(**GRU**)。 GRU 通过将单元状态和隐藏状态组合在一起来简化 LSTM。 GRU 还将门数量减少了一个。 `GRU()`函数也可以用作`SimpleRNN()`的直接替代品。 - -![](img/B14853_01_19.png) - -图 1.5.4:LSTM 图。为了清楚起见,未显示参数。 - -还有许多其他方法可以配置 RNN。 一种方法是制作双向 RNN 模型。 默认情况下,从当前输出仅受过去状态和当前输入影响的意义上讲,RNN 是单向的。 - -在双向 RNN 中,未来状态还可以通过允许信息向后流动来影响当前状态和过去状态。 根据收到的新信息,根据需要更新过去的输出。 可以通过调用包装器函数使 RNN 双向。 例如,双向 LSTM 的实现是`Bidirectional(LSTM())`。 - -对于所有类型的 RNN,增加单元数量也将增加容量。 但是,增加容量的另一种方法是堆叠 RNN 层。 尽管应注意,但作为一般经验法则,只有在需要时才应增加模型的容量。 容量过大可能会导致过拟合,结果可能导致训练时间延长和预测期间的表现降低。 - -# 6\. 总结 - -本章概述了三种深度学习模型(MLP,RNN,CNN),并介绍了 TensorFlow 2 `tf.keras`,这是一个用于快速开发,训练和测试适合于生产环境的深度学习模型的库。 还讨论了 Keras 的顺序 API。 在下一章中,将介绍函数式 API,这将使我们能够构建更复杂的模型,专门用于高级深度神经网络。 - -本章还回顾了深度学习的重要概念,例如优化,正则化和损失函数。 为了便于理解,这些概念是在 MNIST 数字分类的背景下提出的。 - -还讨论了使用人工神经网络(特别是 MLP,CNN 和 RNN)进行 MNIST 数字分类的不同解决方案,它们是深度神经网络的重要组成部分,并讨论了它们的表现指标。 - -了解了深度学习概念以及如何将 Keras 用作工具之后,我们现在可以分析高级深度学习模型。 在下一章讨论了函数式 API 之后,我们将继续执行流行的深度学习模型。 随后的章节将讨论选定的高级主题,例如自回归模型(自编码器,GAN,VAE),深度强化学习,对象检测和分段以及使用互信息的无监督学习。 随附的 Keras 代码实现将在理解这些主题方面发挥重要作用。 - -# 7\. 参考 - -1. `Chollet, François. Keras (2015). https://github.com/keras-team/keras.` -2. `LeCun, Yann, Corinna Cortes, and C. J. Burges. MNIST handwritten digit database. AT&T Labs [Online]. Available: http://yann.lecun.com/exdb/mnist2 (2010).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/02.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/02.md deleted file mode 100644 index d6ddd212..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/02.md +++ /dev/null @@ -1,897 +0,0 @@ -# 二、深度神经网络 - -在本章中,我们将研究深度神经网络。 这些网络在更具挑战性的数据集,如 ImageNet,[CIFAR10](https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf) 和 CIFAR100。 为简洁起见,我们仅关注两个网络: **ResNet** [2] [4]和 **DenseNet** [5]。 尽管我们会更加详细,但重要的是花一点时间介绍这些网络。 - -ResNet 引入了残差学习的概念,使残障学习能够通过解决深度卷积网络中消失的梯度问题(在第 2 节中讨论)来构建非常深的网络。 - -DenseNet 允许每个卷积直接访问输入和较低层的特征映射,从而进一步改进了 ResNet。 通过利用**瓶颈**和**过渡层**,还可以在深层网络中将参数的数量保持为较低。 - -但是,为什么这些是两个模型,而不是其他? 好吧,自从引入它们以来,已经有无数的模型,例如 **ResNeXt** [6]和 **WideResNet** [7],它们受到这两个网络使用的技术的启发。 同样,在了解 ResNet 和 DenseNet 的情况下,我们将能够使用他们的设计指南来构建我们自己的模型。 通过使用迁移学习,这也将使我们能够将预训练的 ResNet 和 DenseNet 模型用于我们自己的目的,例如对象检测和分割。 仅出于这些原因,以及与 Keras 的兼容性,这两个模型非常适合探索和补充本书的高级深度学习范围。 - -尽管本章的重点是深度神经网络; 在本章中,我们将讨论 Keras 的重要功能,称为**函数式 API**。 该 API 充当在`tf.keras`中构建网络的替代方法,使我们能够构建更复杂的网络,而这是顺序模型 API 无法实现的。 我们之所以专注于此 API 的原因是,它将成为构建诸如本章重点介绍的两个之类的深度网络的非常有用的工具。 建议您先完成“第 1 章”,“Keras 的高级深度学习介绍”,然后再继续本章,因为我们将参考在本章中探讨的入门级代码和概念,我们将它们带入了更高的层次。 - -本章的目的是介绍: - -* Keras 中的函数式 API,以及探索运行该 API 的网络示例 -* `tf.keras`中的深度残差网络(ResNet 版本 1 和 2)实现 -* `tf.keras`中密集连接卷积网络(DenseNet)的实现 -* 探索两种流行的深度学习模型,即 **ResNet** 和 **DenseNet** - -让我们开始讨论函数式 API。 - -# 1\. 函数式 API - -在我们首先在“第 1 章”,“Keras 高级深度学习入门”的顺序模型 API 中,一层堆叠在另一层之上。 通常,将通过其输入和输出层访问模型。 我们还了解到,如果我们发现自己想要在网络中间添加辅助输入,或者甚至想在最后一层之前提取辅助输出,则没有简单的机制。 - -这种模式也有缺点。 例如,它不支持类似图的模型或行为类似于 Python 函数的模型。 此外,在两个模型之间共享层也很困难。函数式 API 解决了这些局限性,这就是为什么它对于想要使用深度学习模型的任何人来说都是至关重要的工具的原因。 - -函数式 API 遵循以下两个概念: - -* 层是接受张量作为参数的实例。 一层的输出是另一个张量。 为了构建模型,层实例是通过输入和输出张量彼此链接的对象。 这与在顺序模型中堆叠多个层有类似的最终结果。 但是,使用层实例会使模型更容易具有辅助或多个输入和输出,因为每个层的输入/输出将易于访问。 -* 模型是一个或多个输入张量和输出张量之间的函数。 在模型输入和输出之间,张量是通过层输入和输出张量彼此链接的层实例。 因此,模型是一个或多个输入层和一个或多个输出层的函数。 该模型实例将数据从输入流到输出流的形式的计算图形式化。 - -在完成函数式 API 模型的构建之后,训练和评估将由顺序模型中使用的相同函数执行。 为了说明,在函数式 API 中,二维卷积层`Conv2D`带有 32 个过滤器,并且`x`作为层输入张量,`y`作为层输出张量可以写为: - -```py -y = Conv2D(32)(x) -``` - -我们也可以堆叠多层来构建模型。 例如,我们可以使用函数式 API 重写 MNIST `cnn-mnist-1.4.1.py`上的**卷积神经网络**(**CNN**),如下所示: - -“列表 2.1.1”:`cnn-functional-2.1.1.py` - -```py -import numpy as np -from tensorflow.keras.layers import Dense, Dropout, Input -from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten -from tensorflow.keras.models import Model -from tensorflow.keras.datasets import mnist -from tensorflow.keras.utils import to_categorical -``` - -```py -# load MNIST dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -# from sparse label to categorical -num_labels = len(np.unique(y_train)) -y_train = to_categorical(y_train) -y_test = to_categorical(y_test) -``` - -```py -# reshape and normalize input images -image_size = x_train.shape[1] -x_train = np.reshape(x_train,[-1, image_size, image_size, 1]) -x_test = np.reshape(x_test,[-1, image_size, image_size, 1]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -input_shape = (image_size, image_size, 1) -batch_size = 128 -kernel_size = 3 -filters = 64 -dropout = 0.3 -``` - -```py -# use functional API to build cnn layers -inputs = Input(shape=input_shape) -y = Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu')(inputs) -y = MaxPooling2D()(y) -y = Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu')(y) -y = MaxPooling2D()(y) -y = Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu')(y) -# image to vector before connecting to dense layer -y = Flatten()(y) -# dropout regularization -y = Dropout(dropout)(y) -outputs = Dense(num_labels, activation='softmax')(y) -``` - -```py -# build the model by supplying inputs/outputs -model = Model(inputs=inputs, outputs=outputs) -# network model in text -model.summary() -# classifier loss, Adam optimizer, classifier accuracy -model.compile(loss='categorical_crossentropy', - optimizer='adam', - metrics=['accuracy']) -``` - -```py -# train the model with input images and labels -model.fit(x_train, - y_train, - validation_data=(x_test, y_test), - epochs=20, - batch_size=batch_size) -``` - -```py -# model accuracy on test dataset -score = model.evaluate(x_test, - y_test, - batch_size=batch_size, - verbose=0) -print("\nTest accuracy: %.1f%%" % (100.0 * score[1])) -``` - -默认情况下,使用`pool_size=2`作为参数,因此`MaxPooling2D`已被删除。 - -在前面的清单中,每一层都是张量的函数。 每一层生成一个张量作为输出,该张量成为下一层的输入。 要创建此模型,我们可以调用`Model()`并提供`inputs`和`outputs`张量,或者提供张量列表。 其他一切保持不变。 - -类似于顺序模型,也可以使用`fit()`和`evaluate()`函数来训练和评估相同的列表。 实际上,`Sequential`类是`Model`类的子类。 我们需要记住,我们在`fit()`函数中插入了`validation_data`参数,以查看训练期间验证准确率的进度。 在 20 个周期内,准确率范围从 99.3% 到 99.4%。 - -## 创建两输入一输出模型 - -现在,我们将做一些令人兴奋的事情,创建一个具有两个输入和一个输出的高级模型。 在开始之前,重要的是要知道序列模型 API 是为仅构建 1 输入和 1 输出模型而设计的。 - -假设发明了一种用于 MNIST 数字分类的新模型,它称为 Y 网络,如图“图 2.1.1”所示。 Y 网络在左 CNN 分支和右 CNN 分支两次使用相同的输入。 网络使用`concatenate`层合并结果。 合并操作`concatenate`类似于沿连接轴堆叠两个相同形状的张量以形成一个张量。 例如,沿着最后一个轴连接两个形状为`(3, 3, 16)`的张量将导致一个形状为`(3, 3, 32)`的张量。 - -`concatenate`层之后的所有其他内容将与上一章的 CNN MNIST 分类器模型相同:`Flatten`,然后是`Dropout`,然后是`Dense`: - -![](img/B14853_02_01.png) - -图 2.1.1:Y 网络接受两次相同的输入,但是在卷积网络的两个分支中处理输入。 分支的输出使用连接层进行合并。最后一层的预测将类似于上一章的 CNN MNIST 分类器模型。 - -为了提高“列表 2.1.1”中模型的表现,我们可以提出一些更改。 首先,Y 网络的分支将过滤器数量加倍,以补偿`MaxPooling2D()`之后特征映射尺寸的减半。 例如,如果第一个卷积的输出为`(28, 28, 32)`,则在最大池化之后,新形状为`(14, 14, 32)`。 下一个卷积的过滤器大小为 64,输出尺寸为`(14, 14, 64)`。 - -其次,尽管两个分支的核大小相同,但右分支使用 2 的扩展率。“图 2.1.2”显示了不同的扩展率对大小为 3 的核的影响。 这个想法是,通过使用扩张率增加核的有效接受域大小,CNN 将使正确的分支能够学习不同的特征映射。 使用大于 1 的扩张速率是一种计算有效的近似方法,可以增加接收场的大小。 这是近似值,因为该核实际上不是成熟的核。 这是有效的,因为我们使用与膨胀率等于 1 相同的操作数。 - -要了解接受域的概念,请注意,当核计算特征映射的每个点时,其输入是前一层特征映射中的补丁,该补丁也取决于其前一层特征映射。 如果我们继续将此依赖关系一直跟踪到输入图像,则核将依赖于称为接收场的图像补丁。 - -我们将使用选项`padding='same'`来确保使用扩张的 CNN 时不会出现负张量。 通过使用`padding='same'`,我们将使输入的尺寸与输出特征映射相同。 这是通过用零填充输入以确保输出的**大小**相同来实现的。 - -![](img/B14853_02_02.png) - -图 2.1.2:通过从 1 增加膨胀率,有效的核接受域大小也增加了 - -“列表 2.1.2”的`cnn-y-network-2.1.2.py`显示了使用函数式 API 的 Y 网络的实现。 两个分支由两个`for`循环创建。 两个分支期望输入形状相同。 两个`for`循环将创建两个`Conv2D-Dropout-MaxPooling2D`的三层栈。 虽然我们使用`concatenate`层组合了左右分支的输出,但我们还可以利用`tf.keras`的其他合并函数,例如`add`,`dot`和`multiply`。 合并函数的选择并非纯粹是任意的,而必须基于合理的模型设计决策。 - -在 Y 网络中,`concatenate`不会丢弃特征映射的任何部分。 取而代之的是,我们让`Dense`层确定如何处理连接的特征映射。 - -“列表 2.1.2”:`cnn-y-network-2.1.2.py` - -```py -import numpy as np -from tensorflow.keras.layers import Dense, Dropout, Input -from tensorflow.keras.layers import Conv2D, MaxPooling2D -from tensorflow.keras.layers import Flatten, concatenate -from tensorflow.keras.models import Model -from tensorflow.keras.datasets import mnist -from tensorflow.keras.utils import to_categorical -from tensorflow.keras.utils import plot_model -``` - -```py -# load MNIST dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -# from sparse label to categorical -num_labels = len(np.unique(y_train)) -y_train = to_categorical(y_train) -y_test = to_categorical(y_test) -``` - -```py -# reshape and normalize input images -image_size = x_train.shape[1] -x_train = np.reshape(x_train,[-1, image_size, image_size, 1]) -x_test = np.reshape(x_test,[-1, image_size, image_size, 1]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -input_shape = (image_size, image_size, 1) -batch_size = 32 -kernel_size = 3 -dropout = 0.4 -n_filters = 32 -``` - -```py -# left branch of Y network -left_inputs = Input(shape=input_shape) -x = left_inputs -filters = n_filters -# 3 layers of Conv2D-Dropout-MaxPooling2D -# number of filters doubles after each layer (32-64-128) -for i in range(3): - x = Conv2D(filters=filters, - kernel_size=kernel_size, - padding='same', - activation='relu')(x) - x = Dropout(dropout)(x) - x = MaxPooling2D()(x) - filters *= 2 -``` - -```py -# right branch of Y network -right_inputs = Input(shape=input_shape) -y = right_inputs -filters = n_filters -# 3 layers of Conv2D-Dropout-MaxPooling2Do -# number of filters doubles after each layer (32-64-128) -for i in range(3): - y = Conv2D(filters=filters, - kernel_size=kernel_size, - padding='same', - activation='relu', - dilation_rate=2)(y) - y = Dropout(dropout)(y) - y = MaxPooling2D()(y) - filters *= 2 -``` - -```py -# merge left and right branches outputs -y = concatenate([x, y]) -# feature maps to vector before connecting to Dense -y = Flatten()(y) -y = Dropout(dropout)(y) -outputs = Dense(num_labels, activation='softmax')(y) -``` - -```py -# build the model in functional API -model = Model([left_inputs, right_inputs], outputs) -# verify the model using graph -plot_model(model, to_file='cnn-y-network.png', show_shapes=True) -# verify the model using layer text description -model.summary() -``` - -```py -# classifier loss, Adam optimizer, classifier accuracy -model.compile(loss='categorical_crossentropy', - optimizer='adam', - metrics=['accuracy']) -``` - -```py -# train the model with input images and labels -model.fit([x_train, x_train], - y_train, - validation_data=([x_test, x_test], y_test), - epochs=20, - batch_size=batch_size) -``` - -```py -# model accuracy on test dataset -score = model.evaluate([x_test, x_test], - y_test, - batch_size=batch_size, - verbose=0) -print("\nTest accuracy: %.1f%%" % (100.0 * score[1])) -``` - -退后一步,我们可以注意到 Y 网络期望有两个输入用于训练和验证。 输入是相同的,因此提供了`[x_train, x_train]`。 - -在 20 个周期的过程中,Y 网络的准确率为 99.4% 至 99.5%。 与 3 叠 CNN 相比,这是一个微小的改进,CNN 的精度在 99.3% 到 99.4% 之间。 但是,这是以更高的复杂度和两倍以上的参数数量为代价的。 - -下图“图 2.1.3”显示了 Keras 理解并由`plot_model()`函数生成的 Y 网络的架构: - -![A close up of text on a white background Description automatically generated](img/B14853_02_03.png) - -图 2.1.3:清单 2.1.2 中实现的 CNN Y 网络 - -总结我们对函数式 API 的了解。 我们应该花时间记住本章的重点是构建深度神经网络,特别是 ResNet 和 DenseNet。 因此,我们只讨论构建它们所需的函数式 API 材料,因为涵盖整个的 API 将超出本书的范围。 话虽如此,让我们继续讨论 ResNet。 - -有关函数式 API 的其他信息,请阅读[这里](https://keras.io/)。 - -# 2\. 深度残差网络(ResNet) - -深度网络的一个主要优点是,它们具有从输入图和特征映射学习不同级别表示的能力。 在分类,分割,检测和许多其他计算机视觉问题中,学习不同的特征映射通常可以提高性能。 - -但是,您会发现训练深层网络并不容易,因为在反向传播过程中,梯度可能会随着浅层中的深度消失(或爆炸)。“图 2.2.1”说明了梯度消失的问题。 通过从输出层向所有先前层的反向传播来更新网络参数。 由于反向传播是基于链法则的,因此当梯度到达浅层时,梯度会逐渐减小。 这是由于小数的乘法,尤其是对于小损失函数和参数值。 - -乘法运算的数量将与网络深度成正比。 还要注意的是,如果梯度降低,则不会适当更新参数。 - -因此,网络将无法提高其表现。 - -![](img/B14853_02_04.png) - -图 2.2.1:深层网络中的一个常见问题是,在反向传播过程中,梯度在到达浅层时会消失。 - -为了减轻深度网络中梯度的降级,ResNet 引入了深度残差学习框架的概念。 让我们分析一个块:深度网络的一小部分。 - -“图 2.2.2”显示了典型 CNN 块和 ResNet 残差块之间的比较。 ResNet 的想法是,为了防止梯度降级,我们将让信息通过快捷连接流到浅层。 - -![](img/B14853_02_05.png) - -图 2.2.2:典型 CNN 中的块与 ResNet 中的块之间的比较。 为了防止反向传播期间梯度的降低,引入了快捷连接。 - -接下来,我们将在中讨论两个模块之间的差异,以了解更多详细信息。“图 2.2.3”显示了另一个常用的深层网络 **VGG** [3]和 ResNet 的 CNN 块的更多详细信息。 我们将层特征映射表示为`x`。 层`l`的特征映射为`x[l]`。 在 CNN 层中的操作是 **Conv2D 批量规范化(BN)- ReLU**。 - -假设我们以`H() = Conv2D-Batch Normalization(BN)-ReLU`的形式表示这组操作; 然后: - -`x[l-1] = H(x[l-2])`(公式 2.2.1) - -`x[l] = H(x[l-1])`(方程式 2.2.2) - -换句话说,通过`H() =Conv2D-Batch Normalization(BN)-ReLU`将`l-2`层上的特征映射转换为`x[l-1]`。 应用相同的操作集将`x[l-1]`转换为`x[l]`。 换句话说,如果我们有一个 18 层的 VGG,则在将输入图像转换为第 18 个层特征映射之前,有 18 个`H()`操作。 - -一般而言,我们可以观察到`l`层输出特征映射仅直接受先前的特征映射影响。 同时,对于 ResNet: - -`x[l-1] = H(x[l-2])`(公式 2.2.3) - -`x[l] = ReLU(F(x[l-1]) + x[l-2])`(公式 2.2.4) - -![](img/B14853_02_06.png) - -图 2.2.3:普通 CNN 块和残差块的详细层操作 - -`F(x[l-1])`由`Conv2D-BN`制成,这也被称为残差映射。 `+`符号是快捷方式连接和`F(x[l-1])`输出之间的张量元素加法。 快捷连接不会增加额外的参数,也不会增加计算复杂度。 - -可以通过`add()`合并函数在`tf.keras`中实现添加操作。 但是,`F(x[l-1])`和`x[l-2]`应该具有相同的尺寸。 - -如果尺寸不同,例如,当更改特征映射尺寸时,我们应该在`x[l-2]`上进行线性投影以匹配尺寸`F([l-1])`的含量。 在原始论文中,当特征映射的大小减半时,情况的线性投影是通过`Conv2D`和 1 `strides=2`核完成的。 - -在“第 1 章”,“Keras 高级深度学习”,我们讨论了`stride > 1`等效于在卷积期间跳过像素。 例如,如果`strides=2`,则在卷积过程中滑动核时,可以跳过其他每个像素。 - -前面的“公式 2.2.3”和“公式 2.2.4”都对 ResNet 残余块操作进行建模。 他们暗示,如果可以训练较深的层具有较少的误差,则没有理由为什么较浅的层应具有较高的误差。 - -知道 ResNet 的基本构建块后,我们就可以设计一个深度残差网络来进行图像分类。 但是,这一次,我们将处理更具挑战性的数据集。 - -在我们的示例中,我们将考虑 CIFAR10,它是原始论文所基于的数据集之一。 在此示例中,`tf.keras`提供了一个 API,可以方便地访问 CIFAR10 数据集,如下所示: - -```py -from tensorflow.keras.datasets import cifar10 -(x_train, y_train), (x_test, y_test) = cifar10.load_data() -``` - -与 MNIST 一样,CIFAR10 数据集也有 10 个类别。 数据集是对应于飞机,汽车,鸟,猫,鹿,狗,青蛙,马,船和卡车的小型(`32×32`)RGB 真实世界图像的集合。 10 个类别中的每个类别。“图 2.2.4”显示了来自 CIFAR10 的示例图像。 - -在数据集中,有 50,000 个标记的训练图像和 10,000 个标记的测试图像用于验证: - -![](img/B14853_02_07.png) - -图 2.2.4:来自 CIFAR10 数据集的样本图像。 完整的数据集包含 50,000 张标签的训练图像和 10,000 张标签的测试图像以进行验证。 - -对于 CIFAR10 数据,可以使用“表 2.2.1”中所示的不同网络架构来构建 ResNet。“表 2.2.1”表示我们有三组残差块。 每组具有对应于`n`个残余块的`2n`层。`32×32`的额外层是输入图像的第一层。 - -| **层** | **输出大小** | **过滤器尺寸** | **操作** | -| --- | --- | --- | --- | -| 卷积 | `32 × 32` | 16 | `3 x 3 Conv2D` | -| 残差块(1) | `32 × 32` | | ![](img/B14853_02_001.png) | -| 过渡层(1) | `32 × 32` | | `{1 x 1 Conv2D, stride = 2}` | -| | `16 × 16` | | -| 残差块(2) | `16 × 16` | 32 | ![](img/B14853_02_002.png) | -| 过渡层(2) | `16 × 16` | | | | `{1 x 1 Conv2D, stride = 2}` | -| | `8 × 8` | | -| 残差块(3) | `8 × 8` | 64 | ![](img/B14853_02_003.png) | -| 平均池化 | `1 × 1` | | | `8 x 8 AveragePooling2D` | - -表 2.2.1:ResNet 网络架构配置 - -核大小为 3,不同大小的两个特征映射之间的过渡除外,该过渡实现了线性映射。 例如,核大小为 1 的`Conv2D`和`strides=2`。 为了与 DenseNet 保持一致,当我们连接两个大小不同的剩余块时,我们将使用项`Transition`层。 - -ResNet 使用`kernel_initializer='he_normal'`以便在进行反向传播时帮助收敛[1]。 最后一层由`AveragePooling2D-Flatten-Dense`制成。 在这一点上值得注意的是 ResNet 不使用丢弃。 似乎`add` 合并操作和`1 x 1`卷积具有自正则化效果。“图 2.2.5”显示了 CIFAR10 数据集的 ResNet 模型架构,如“表 2.2.1”中所述。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_02_08.png) - -图 2.2.5:用于 CIFAR10 数据集分类的 ResNet 的模型架构 - -以下代码段显示了`tf.keras`中的部分 ResNet 实现。 该代码已添加到 Keras GitHub 存储库中。 从“表 2.2.2”(稍后显示)中,我们还可以看到,通过修改`n`的值,我们可以增加网络的深度。 - -例如,对于`n = 18`,我们已经拥有 ResNet110,这是一个具有 110 层的深度网络。 要构建 ResNet20,我们使用`n = 3`: - -```py -n = 3 -``` - -```py -# model version -# orig paper: version = 1 (ResNet v1), -# improved ResNet: version = 2 (ResNet v2) -version = 1 -``` - -```py -# computed depth from supplied model parameter n -if version == 1: - depth = n * 6 + 2 -elif version == 2: - depth = n * 9 + 2 -``` - -```py - if version == 2: - model = resnet_v2(input_shape=input_shape, depth=depth) -else: - model = resnet_v1(input_shape=input_shape, depth=depth) -``` - -`resnet_v1()`方法是 ResNet 的模型构建器。 它使用工具函数`resnet_layer(),`来帮助构建`Conv2D-BN-ReLU`的栈。 - -它将称为版本 1,正如我们将在下一节中看到的那样,提出了一种改进的 ResNet,该版本称为 ResNet 版本 2 或 v2。 通过 ResNet,ResNet v2 改进了残差块设计,从而提高了表现。 - -以下清单显示了`resnet-cifar10-2.2.1.py`的部分代码,它是 ResNet v1 的`tf.keras`模型实现。 - -“列表 2.2.1”:`resnet-cifar10-2.2.1.py` - -```py -def resnet_v1(input_shape, depth, num_classes=10): - """ResNet Version 1 Model builder [a] -``` - -```py - Stacks of 2 x (3 x 3) Conv2D-BN-ReLU - Last ReLU is after the shortcut connection. - At the beginning of each stage, the feature map size is halved - (downsampled) by a convolutional layer with strides=2, while - the number of filters is doubled. Within each stage, - the layers have the same number filters and the - same number of filters. - Features maps sizes: - stage 0: 32x32, 16 - stage 1: 16x16, 32 - stage 2: 8x8, 64 - The Number of parameters is approx the same as Table 6 of [a]: - ResNet20 0.27M - ResNet32 0.46M - ResNet44 0.66M - ResNet56 0.85M - ResNet110 1.7M -``` - -```py - Arguments: - input_shape (tensor): shape of input image tensor - depth (int): number of core convolutional layers - num_classes (int): number of classes (CIFAR10 has 10) -``` - -```py - Returns: - model (Model): Keras model instance - """ - if (depth - 2) % 6 != 0: - raise ValueError('depth should be 6n+2 (eg 20, 32, in [a])') - # Start model definition. - num_filters = 16 - num_res_blocks = int((depth - 2) / 6) -``` - -```py - inputs = Input(shape=input_shape) - x = resnet_layer(inputs=inputs) - # instantiate the stack of residual units - for stack in range(3): - for res_block in range(num_res_blocks): - strides = 1 - # first layer but not first stack - if stack > 0 and res_block == 0: - strides = 2 # downsample - y = resnet_layer(inputs=x, - num_filters=num_filters, - strides=strides) - y = resnet_layer(inputs=y, - num_filters=num_filters, - activation=None) - # first layer but not first stack - if stack > 0 and res_block == 0: - # linear projection residual shortcut - # connection to match changed dims - x = resnet_layer(inputs=x, - num_filters=num_filters, - kernel_size=1, - strides=strides, - activation=None, - batch_normalization=False) - x = add([x, y]) - x = Activation('relu')(x) - num_filters *= 2 -``` - -```py - # add classifier on top. - # v1 does not use BN after last shortcut connection-ReLU - x = AveragePooling2D(pool_size=8)(x) - y = Flatten()(x) - outputs = Dense(num_classes, - activation='softmax', - kernel_initializer='he_normal')(y) -``` - -```py - # instantiate model. - model = Model(inputs=inputs, outputs=outputs) - return model -``` - -ResNet 在`n`的各种值上的表现显示在“表 2.2.2”中。 - -| **层** | `n` | **CIFAR10 的准确率百分比(原始论文)** | **CIFAR10 的准确率百分比(本书)** | -| --- | --- | --- | --- | -| ResNet20 | 3 | 91.25 | 92.16 | -| ResNet32 | 5 | 92.49 | 92.46 | -| ResNet44 | 7 | 92.83 | 92.50 | -| ResNet56 | 9 | 93.03 | 92.71 | -| ResNet110 | 18 | 93.57 | 92.65 | - -表 2.2.2:针对不同的 n 值,使用 CIFAR10 验证的 ResNet 架构 - -与 ResNet 的原始实现有一些细微的差异。 特别是,我们不使用 SGD,而是使用 Adam。 这是因为 ResNet 更容易与 Adam 融合。 我们还将使用学习率调度器`lr_schedule()`,以便将`lr`的减少量从默认的`1e-3`缩短为 80、120、160 和 180 个周期。 在训练期间的每个周期之后,都会将`lr_schedule()`函数作为回调变量的一部分进行调用。 - -每当验证准确率方面取得进展时,另一个回调将保存检查点。 训练深层网络时,保存模型或权重检查点是一个好习惯。 这是因为训练深度网络需要大量时间。 - -当您想使用网络时,您只需要做的就是重新加载检查点,然后恢复经过训练的模型。 这可以通过调用`tf.keras load_model()`来完成。 包含`lr_reducer()`函数。 如果指标在排定的减少之前已稳定在上,则如果在`patience = 5`周期之后验证损失没有改善,则此回调将以参数中提供的某个因子来降低学习率。 - -调用`model.fit()`方法时,会提供**回调**变量。 与原始论文相似,`tf.keras`实现使用数据扩充`ImageDataGenerator()`来提供其他训练数据作为正则化方案的一部分。 随着训练数据数量的增加,概括性将会提高。 - -例如,简单的数据扩充就是翻转一条狗的照片,如图“图 2.2.6”(`horizontal_flip = True`)所示。 如果它是狗的图像,则翻转的图像仍然是狗的图像。 您还可以执行其他变换,例如缩放,旋转,变白等等,并且标签将保持不变: - -![A brown and white dog looking at the camera Description automatically generated](img/B14853_02_09.png) - -图 2.2.6:一个简单的数据扩充就是翻转原始图像 - -[完整的代码可在 GitHub 上获得](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -准确复制原始论文的实现通常很困难。 在本书中,我们使用了不同的优化器和数据扩充。 这可能会导致本书中所实现的`tf.keras` ResNet 和原始模型中的表现略有不同。 - -在 **ResNet** [4]的第二篇论文发布之后,本节中介绍的原始模型为,称为 ResNet v1。 改进的 ResNet 通常称为 ResNet v2,我们将在下一部分讨论。 - -# 3\. ResNet v2 - -ResNet v2 的改进主要体现在残块中各层的排列中,如图“图 2.3.1”所示。 - -ResNet v2 的主要变化是: - -* 使用`1 x 1 – 3 x 3 – 1 × 1`的栈`BN-ReLU-Conv2D` -* 批量标准化和 ReLU 激活先于二维卷积 - -![](img/B14853_02_10.png) - -图 2.3.1:ResNet v1 和 ResNet v2 之间的剩余块比较 - -ResNet v2 也以与`resnet-cifar10-2.2.1.py`相同的代码实现,如“列表 2.2.1”所示: - -“列表 2.2.1”:`resnet-cifar10-2.2.1.py` - -```py -def resnet_v2(input_shape, depth, num_classes=10): - """ResNet Version 2 Model builder [b] -``` - -```py - Stacks of (1 x 1)-(3 x 3)-(1 x 1) BN-ReLU-Conv2D or - also known as bottleneck layer. - First shortcut connection per layer is 1 x 1 Conv2D. - Second and onwards shortcut connection is identity. - At the beginning of each stage, - the feature map size is halved (downsampled) - by a convolutional layer with strides=2, - while the number of filter maps is - doubled. Within each stage, the layers have - the same number filters and the same filter map sizes. - Features maps sizes: - conv1 : 32x32, 16 - stage 0: 32x32, 64 - stage 1: 16x16, 128 - stage 2: 8x8, 256 -``` - -```py - Arguments: - input_shape (tensor): shape of input image tensor - depth (int): number of core convolutional layers - num_classes (int): number of classes (CIFAR10 has 10) -``` - -```py - Returns: - model (Model): Keras model instance - """ - if (depth - 2) % 9 != 0: - raise ValueError('depth should be 9n+2 (eg 110 in [b])') - # start model definition. - num_filters_in = 16 - num_res_blocks = int((depth - 2) / 9) -``` - -```py - inputs = Input(shape=input_shape) - # v2 performs Conv2D with BN-ReLU - # on input before splitting into 2 paths - x = resnet_layer(inputs=inputs, - num_filters=num_filters_in, - conv_first=True) -``` - -```py - # instantiate the stack of residual units - for stage in range(3): - for res_block in range(num_res_blocks): - activation = 'relu' - batch_normalization = True - strides = 1 - if stage == 0: - num_filters_out = num_filters_in * 4 - # first layer and first stage - if res_block == 0: - activation = None - batch_normalization = False - else: - num_filters_out = num_filters_in * 2 - # first layer but not first stage - if res_block == 0: - # downsample - strides = 2 -``` - -```py - # bottleneck residual unit - y = resnet_layer(inputs=x, - num_filters=num_filters_in, - kernel_size=1, - strides=strides, - activation=activation, - batch_normalization=batch_normalization, - conv_first=False) - y = resnet_layer(inputs=y, - num_filters=num_filters_in, - conv_first=False) - y = resnet_layer(inputs=y, - num_filters=num_filters_out, - kernel_size=1, - conv_first=False) - if res_block == 0: - # linear projection residual shortcut connection - # to match changed dims - x = resnet_layer(inputs=x, - num_filters=num_filters_out, - kernel_size=1, - strides=strides, - activation=None, - batch_normalization=False) - x = add([x, y]) -``` - -```py - num_filters_in = num_filters_out -``` - -```py - # add classifier on top. - # v2 has BN-ReLU before Pooling - x = BatchNormalization()(x) - x = Activation('relu')(x) - x = AveragePooling2D(pool_size=8)(x) - y = Flatten()(x) - outputs = Dense(num_classes, - activation='softmax', - kernel_initializer='he_normal')(y) -``` - -```py - # instantiate model. - model = Model(inputs=inputs, outputs=outputs) - return model -``` - -下面的代码显示了 ResNet v2 的模型构建器。 例如,要构建 ResNet110 v2,我们将使用`n = 12`和`version = 2`: - -```py -n = 12 -``` - -```py -# model version -# orig paper: version = 1 (ResNet v1), -# improved ResNet: version = 2 (ResNet v2) -version = 2 -``` - -```py -# computed depth from supplied model parameter n -if version == 1: - depth = n * 6 + 2 -elif version == 2: - depth = n * 9 + 2 -``` - -```py - if version == 2: - model = resnet_v2(input_shape=input_shape, depth=depth) -else: - model = resnet_v1(input_shape=input_shape, depth=depth) -``` - -ResNet v2 的准确率显示在下面的“表 2.3.1”中: - -| **层** | `n` | **CIFAR10 的准确率百分比(原始论文)** | **CIFAR10 的准确率百分比(本书)** | -| --- | --- | --- | --- | -| ResNet56 | 9 | 不适用 | 93.01 | -| ResNet110 | 18 | 93.63 | 93.15 | - -表 2.3.1:在 CIFAR10 数据集上验证的 ResNet v2 架构 - -在 Keras 应用包中,已实现某些 ResNet v1 和 v2 模型(例如:50、101、152)。 这些是替代的实现方式,其中预训练的权重不清楚,可以轻松地重新用于迁移学习。 本书中使用的模型在层数方面提供了灵活性。 - -我们已经完成了对最常用的深度神经网络之一 ResNet v1 和 v2 的讨论。 在以下部分中,将介绍另一种流行的深度神经网络架构 DenseNet。 - -# 4\. 紧密连接的卷积网络(DenseNet) - -![](img/B14853_02_11.png) - -图 2.4.1:DenseNet 中的一个 4 层`Dense`块,每层的输入均由所有先前的特征映射组成。 - -DenseNet 使用另一种方法攻击梯度消失的问题。 代替使用快捷方式连接,所有先前的特征映射都将成为下一层的输入。 上图显示了一个`Dense`块中密集互连的示例。 - -为简单起见,在此图中,我们仅显示四层。 注意,层`l`的输入是所有先前特征映射的连接。 如果用操作`H`表示`BN-ReLU-Conv2D`(`x`),则层`l`的输出为: - -`x[l] = H(x[0], x[1], x[2], x[l-1])`(公式 2.4.1) - -`Conv2D`使用大小为 3 的核。每层生成的特征映射的数量称为增长率`k`。 通常,在 Huang 等人的论文“密集连接卷积网络”中,也使用`k = 12`,但是`k = 24` [5]。 因此,如果特征映射`x[0]`的数量为`k[0]`,则“图 2.4.1”中,4 层`Dense`块的末尾的特征映射总数为`4 x k + k[0]`。 - -DenseNet 建议在`Dense`块之前加上`BN-ReLU-Conv2D`,以及许多是增长率两倍的特征映射`k[0]`= 2 x`k`。 在`Dense`块的末尾,特征映射的总数将为`4 x 12 + 2 x 12 = 72`。 - -在输出层,DenseNet 建议我们在具有`softmax`层的`Dense()`之前执行平均池化。 如果未使用数据扩充,则必须在`Dense`块`Conv2D`之后跟随一个丢弃层。 - -随着网络的深入,将出现两个新问题。 首先,由于每一层都贡献了`k`特征映射,因此`l`层的输入数量为`(l – 1) x k + k[0]`。 特征映射可以在深层中快速增长,从而减慢了计算速度。 例如,对于 101 层网络,对于`k = 12`,这将是`1200 + 24 = 1224`。 - -其次,类似于 ResNet,随着网络的不断深入,特征映射的大小将减小,从而增加核的接收域大小。 如果 DenseNet 在合并操作中使用连接,则必须协调大小上的差异。 - -为了防止特征映射的数量增加到计算效率低的程度,DenseNet 引入了`Bottleneck`层,如图“图 2.4.2”所示。 这个想法是,在每次连接之后,现在应用`1 x 1`卷积,其过滤器大小等于`4k`。 这种降维技术阻止了`Conv2D(3)`处理的特征映射的数量快速增加。 - -![](img/B14853_02_12.png) - -图 2.4.2:DenseNet 的 Dense 块中的一层,带有和不带有瓶颈层 BN-ReLU-Conv2D(1)。 为了清楚起见,我们将核大小作为 Conv2D 的参数。 - -然后`Bottleneck`层将 DenseNet 层修改为`BN-ReLU-Conv2D(1)-BN- ReLU-Conv2D(3)`,而不仅仅是`BN-ReLU-Conv2D(3)`。 为了清楚起见,我们将核大小作为`Conv2D`的参数。 在瓶颈层,每个`Conv2D(3)`仅处理 4 个`k`特征映射,而不是`(l – 1) x k + k[0]`的,对于层`l`。 例如,对于 101 层网络,最后一个`Conv2D(3)`的输入仍然是`k = 12`而不是先前计算的 1224 的 48 个特征映射。 - -为了解决特征映射大小不匹配的问题,DenseNet 将深度网络划分为多个 Dense 块,这些块通过过渡层连接在一起,如图“图 2.4.3”所示。 在每个`Dense`块中,特征映射的大小(即宽度和高度)将保持不变。 - -过渡层的作用是在两个`Dense`块之间从一个特征映射大小过渡到较小的特征映射大小。 尺寸通常减少一半。 这是通过平均池化层完成的。 例如,默认值为`pool_size=2`的`AveragePooling2D`会将大小从`(64, 64, 256)`减小为`(32, 32, 256)`。 过渡层的输入是前一个`Dense`块中最后一个连接层的输出。 - -![A close up of a logo Description automatically generated](img/B14853_02_13.png) - -图 2.4.3:两个密集块之间的过渡层 - -但是,在将特征映射传递到平均池之前,使用`Conv2D(1)`将其数量减少某个压缩因子`0 < θ < 1`。DenseNet 在实验中使用`θ = 0.5`。 例如,如果先前`Dense`块的最后连接的输出是`(64, 64, 512)`,则在`Conv2D(1)`之后,特征映射的新尺寸将是`(64, 64, 256)`。 当压缩和降维放在一起时,过渡层由`BN-Conv2D(1)-AveragePooling2D`层组成。 实际上,批量归一化在卷积层之前。 - -现在,我们已经涵盖了 DenseNet 的重要概念。 接下来,我们将为`tf.keras`中的 CIFAR10 数据集构建并验证 DenseNet-BC。 - -## 为 CIFAR10 构建 100 层 DenseNet-BC - -现在,我们将要为 CIFAR10 数据集构建一个具有 100 层的 **DenseNet-BC**(**瓶颈压缩**), 我们在上面讨论过。 - -“表 2.4.1”显示了模型配置,而“图 2.4.4”显示了模型架构。 清单为我们展示了具有 100 层的 DenseNet-BC 的部分 Keras 实现。 我们需要注意的是,我们使用`RMSprop`,因为在使用 DenseNet 时,它的收敛性优于 SGD 或 Adam。 - -| **层** | **输出大小** | **DenseNet-100 BC** | -| --- | --- | --- | -| 卷积 | `32 x 32` | `3 x 3 Conv2D` | -| 密集块(1) | `32 x 32` | ![](img/B14853_02_006.png) | -| 过渡层(1) | `32 x 32` | ![](img/B14853_02_007.png) | -| `16 x 16` | -| 密集块(2) | `16 x 16` | ![](img/B14853_02_008.png) | -| 过渡层(2) | `16 x 16` | ![](img/B14853_02_009.png) | -| `8 x 8` | -| 密集块(3) | `8 x 8` | ![](img/B14853_02_006.png) | -| 平均池化 | `1 x 1` | `8 x 8 AveragePooling2D` | -| 分类层 | | `Flatten-Dense(10)-softmax` | - -表 2.4.1:100 层的 DenseNet-BC 用于 CIFAR10 分类 - -将从配置移至架构: - -![A screenshot of a cell phone Description automatically generated](img/B14853_02_14.png) - -图 2.4.4:用于 CIFAR10 分类的 100 个层的 DenseNet-BC 模型架构 - -下面“列表 2.4.1”是具有 100 层的 DenseNet-BC 的部分 Keras 实现,如“表 2.4.1”所示。 - -“列表 2.4.1”:`densenet-cifar10-2.4.1.py` - -```py -# start model definition -# densenet CNNs (composite function) are made of BN-ReLU-Conv2D -inputs = Input(shape=input_shape) -x = BatchNormalization()(inputs) -x = Activation('relu')(x) -x = Conv2D(num_filters_bef_dense_block, - kernel_size=3, - padding='same', - kernel_initializer='he_normal')(x) -x = concatenate([inputs, x]) -``` - -```py -# stack of dense blocks bridged by transition layers -for i in range(num_dense_blocks): - # a dense block is a stack of bottleneck layers - for j in range(num_bottleneck_layers): - y = BatchNormalization()(x) - y = Activation('relu')(y) - y = Conv2D(4 * growth_rate, - kernel_size=1, - padding='same', - kernel_initializer='he_normal')(y) - if not data_augmentation: - y = Dropout(0.2)(y) - y = BatchNormalization()(y) - y = Activation('relu')(y) - y = Conv2D(growth_rate, - kernel_size=3, - padding='same', - kernel_initializer='he_normal')(y) - if not data_augmentation: - y = Dropout(0.2)(y) - x = concatenate([x, y]) -``` - -```py - # no transition layer after the last dense block - if i == num_dense_blocks - 1: - continue - # transition layer compresses num of feature maps and # reduces the size by 2 - num_filters_bef_dense_block += num_bottleneck_layers * growth_rate - num_filters_bef_dense_block = int(num_filters_bef_dense_block * compression_factor) - y = BatchNormalization()(x) - y = Conv2D(num_filters_bef_dense_block, - kernel_size=1, - padding='same', - kernel_initializer='he_normal')(y) - if not data_augmentation: - y = Dropout(0.2)(y) - x = AveragePooling2D()(y) -``` - -```py -# add classifier on top -# after average pooling, size of feature map is 1 x 1 -x = AveragePooling2D(pool_size=8)(x) -y = Flatten()(x) -outputs = Dense(num_classes, - kernel_initializer='he_normal', - activation='softmax')(y) -# instantiate and compile model -# orig paper uses SGD but RMSprop works better for DenseNet -model = Model(inputs=inputs, outputs=outputs) -model.compile(loss='categorical_crossentropy', - optimizer=RMSprop(1e-3), - metrics=['accuracy']) -model.summary() -``` - -训练 DenseNet 的`tf.keras`实现 200 个周期,可以达到 93.74% 的准确率,而本文中报道的是 95.49%。 使用数据扩充。 我们在 ResNet v1 / v2 中为 DenseNet 使用了相同的回调函数。 - -对于更深的层,必须使用 Python 代码上的表来更改`growth_rate`和`depth`变量。 但是,如本文所述,以深度 190 或 250 训练网络将需要大量时间。 为了给我们一个训练时间的想法,每个周期在 1060Ti GPU 上运行大约一个小时。 与 ResNet 相似,Keras 应用包具有针对 DenseNet 121 及更高版本的预训练模型。 - -DenseNet 完成了我们对深度神经网络的讨论。 与 ResNet 一起,这两个网络已成为许多下游任务中不可或缺的特征提取器网络。 - -# 5\. 总结 - -在本章中,我们介绍了函数式 API 作为使用`tf.keras`构建复杂的深度神经网络模型的高级方法。 我们还演示了如何使用函数式 API 来构建多输入单输出 Y 网络。 与单分支 CNN 网络相比,该网络具有更高的准确率。 在本书的其余部分中,我们将发现在构建更复杂和更高级的模型时必不可少的函数式 API。 例如,在下一章中,函数式 API 将使我们能够构建模块化编码器,解码器和自编码器。 - -我们还花费了大量时间探索两个重要的深度网络 ResNet 和 DenseNet。 这两个网络不仅用于分类,而且还用于其他领域,例如分段,检测,跟踪,生成和视觉语义理解。 在“第 11 章”,“对象检测”和“第 12 章”,“语义分割”中,我们将使用 ResNet 进行对象检测和分割。 我们需要记住,与仅仅遵循原始实现相比,更仔细地了解 ResNet 和 DenseNet 中的模型设计决策至关重要。 这样,我们就可以将 ResNet 和 DenseNet 的关键概念用于我们的目的。 - -# 6\. 参考 - -1. `Kaiming He et al. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. Proceedings of the IEEE international conference on computer vision, 2015 (https://www.cv-foundation.org/openaccess/content_iccv_2015/papers/He_Delving_Deep_into_ICCV_2015_paper.pdfspm=5176.100239.blogcont55892.28.pm8zm1&file=He_Delving_Deep_into_ICCV_2015_paper.pdf).` -1. `Kaiming He et al. Deep Residual Learning for Image Recognition. Proceedings of the IEEE conference on computer vision and pattern recognition, 2016a (http://openaccess.thecvf.com/content_cvpr_2016/papers/He_Deep_Residual_Learning_CVPR_2016_paper.pdf).` -1. `Karen Simonyan and Andrew Zisserman. Very Deep Convolutional Networks for Large-Scale Image Recognition. ICLR, 2015 (https://arxiv.org/pdf/1409.1556/).` -1. `Kaiming He et al. Identity Mappings in Deep Residual Networks. European Conference on Computer Vision. Springer International Publishing, 2016b (https://arxiv.org/pdf/1603.05027.pdf).` -1. `Gao Huang et al. Densely Connected Convolutional Networks. Proceedings of the IEEE conference on computer vision and pattern recognition, 2017 (http://openaccess.thecvf.com/content_cvpr_2017/papers/Huang_Densely_Connected_Convolutional_CVPR_2017_paper.pdf).` -1. `Saining Xie et al. Aggregated Residual Transformations for Deep Neural Networks. Computer Vision and Pattern Recognition (CVPR), 2017 IEEE Conference on. IEEE, 2017 (http://openaccess.thecvf.com/content_cvpr_2017/papers/Xie_Aggregated_Residual_Transformations_CVPR_2017_paper.pdf).` -1. `Zagoruyko, Sergey, and Nikos Komodakis. "Wide residual networks." arXiv preprint arXiv:1605.07146 (2016).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/03.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/03.md deleted file mode 100644 index a59f4ebf..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/03.md +++ /dev/null @@ -1,891 +0,0 @@ -# 三、自编码器 - -在上一章“第 2 章”,“深度神经网络”中,我们介绍了深度神经网络的概念。 现在,我们将继续研究自编码器,它是一种神经网络架构,试图找到给定输入数据的压缩表示形式。 - -与前面的章节相似,输入数据可以采用多种形式,包括语音,文本,图像或视频。 自编码器将尝试查找表示形式或一段代码,以便对输入数据执行有用的转换。 例如,当对自编码器进行降噪处理时,神经网络将尝试找到可用于将噪声数据转换为干净数据的代码。 嘈杂的数据可以是带有静态噪声的录音形式,然后将其转换为清晰的声音。 自编码器将自动从数据中自动学习代码,而无需人工标记。 这样,自编码器可以在**无监督**学习算法下分类为。 - -在本书的后续章节中,我们将研究**生成对抗网络**(**GAN**)和**变分自编码器**(**VAE**) 也是无监督学习算法的代表形式。 这与我们在前几章中讨论过的监督学习算法相反,后者需要人工标注。 - -总之,本章介绍: - -* 自编码器的原理 -* 如何使用`tf.keras`实现自编码器 -* 去噪和着色自编码器的实际应用 - -让我们从了解自编码器是什么以及自编码器的原理开始。 - -# 1\. 自编码器的原理 - -自编码器以最简单的形式通过尝试将输入复制到输出中来学习表示形式或代码。 但是,使用自编码器并不像将输入复制到输出那样简单。 否则,神经网络将无法发现输入分布中的隐藏结构。 - -自编码器将输入分布编码为低维张量,通常采用向量形式。 这将近似通常称为潜在表示,代码或向量的隐藏结构。 该处理构成编码部分。 然后,潜在向量将由解码器部分解码,以恢复原始输入。 - -由于潜向量是输入分布的低维压缩表示,因此应该期望解码器恢复的输出只能近似输入。 输入和输出之间的差异可以通过损失函数来衡量。 - -但是为什么我们要使用自编码器? 简而言之,自编码器在原始形式或更复杂的神经网络的一部分中都有实际应用。 - -它们是了解深度学习的高级主题的关键工具,因为它们为我们提供了适合密度估计的低维数据表示。 此外,可以有效地对其进行处理以对输入数据执行结构化操作。 常见的操作包括去噪,着色,特征级算术,检测,跟踪和分割,仅举几例。 - -在本节中,我们将介绍自编码器的原理。 我们将使用前几章介绍的带有 MNIST 数据集的自编码器。 - -首先,我们需要意识到自编码器具有两个运算符,它们是: - -* **编码器**:这会将输入`x`转换为低维潜向量`z = f(x)`。 由于潜向量是低维的,编码器被迫仅学习输入数据的最重要特征。 例如,在 MNIST 数字的情况下,要学习的重要特征可能包括书写风格,倾斜角度,笔触圆度,厚度等。 从本质上讲,这些是代表数字 0 至 9 所需的最重要的信息位。 -* **解码器**:这尝试从潜在向量`g(z) = x`中恢复输入。 - -尽管潜向量的维数较小,但它的大小足以使解码器恢复输入数据。 - -解码器的目标是使`x_tilde`尽可能接近`x`。 通常,编码器和解码器都是非线性函数。`z`的尺寸是可以表示的重要特征数量的度量。 该维数通常比输入维数小得多,以提高效率,并为了限制潜在代码仅学习输入分布的最显着属性[1]。 - -当潜码的维数明显大于`x`时,自编码器倾向于记忆输入。 - -合适的损失函数`L(x, x_tilde)`衡量输入`x`与输出(即)恢复后的输入`x_tilde`的相异程度。 如下式所示,均方误差(MSE)是此类损失函数的一个示例: - -![](img/B14853_03_006.png) (Equation 3.1.1) - -在此示例中,`m`是输出尺寸(例如,在 MNIST 中,`m = width × height × channels = 28 × 28 × 1 = 784`)。`x[i]`和`x_tilde[i]`分别是`x`和`x_tilde`的元素。 由于损失函数是输入和输出之间差异的量度,因此我们可以使用替代的重建损失函数,例如二进制交叉熵或结构相似性指数(SSIM)。 - -与其他神经网络类似,自编码器会在训练过程中尝试使此误差或损失函数尽可能小。“图 3.1.1”显示了一个自编码器。 编码器是将输入`x`压缩为低维潜向量`z`的函数。 该潜向量代表输入分布的重要特征。 然后,解码器尝试以`x_tilde`的形式从潜向量中恢复原始输入。 - -![](img/B14853_03_01.png) - -图 3.1.1:自编码器的框图 - -为了将自编码器置于上下文中,`x`可以是尺寸为`28×28×1 = 784`的 MNIST 数字。编码器将输入转换为低维的`z`,可以是 16 维潜在向量。 解码器将尝试从`z`中以`x_tilde`的形式恢复输入。 - -在视觉上,每个 MNIST 数字`x`看起来都类似于`x_tilde`。“图 3.1.2”向我们演示了此自编码过程。 - -![](img/B14853_03_02.png) - -图 3.1.2:带有 MNIST 数字输入和输出的自编码器。 潜在向量为 16 角 - -我们可以看到,虽然解码后的数字 7 并不完全相同,但仍然足够接近。 - -由于编码器和解码器都是非线性函数,因此我们可以使用神经网络来实现两者。 例如,在 MNIST 数据集中,自编码器可以由 MLP 或 CNN 实现。 通过最小化通过反向传播的损失函数,可以训练自编码器。 与其他神经网络类似,反向传播的要求是损失函数必须是可微的。 - -如果将输入视为分布,则可以将编码器解释为分布的编码器,`p(z | x)`,将解码器解释为分布的解码器`p(x | z)`。 自编码器的损失函数表示为: - -![](img/B14853_03_012.png) (Equation 3.1.2) - -损失函数只是意味着我们要在给定潜在向量分布的情况下最大程度地恢复输入分布的机会。 如果假设解码器的输出分布为为高斯,则损失函数归结为 MSE,因为: - -![](img/B14853_03_013.png) (Equation 3.1.3) - -在此示例中,`N(x[i]; x_tilde[i], σ²`表示平均值为`x_tilde[i]`且方差为`σ²`的高斯分布。 假设恒定方差。 假定解码器输出`x_tilde[i]`是独立的。`m`是输出尺寸。 - -了解自编码器背后的原理将有助于我们执行代码。 在下一节中,我们将研究如何使用`tf.keras`函数式 API 来构建编码器,解码器和自编码器。 - -# 2\. 使用 Keras 构建自编码器 - -现在,我们要使用进行一些令人兴奋的事情,使用`tf.keras`库构建一个自编码器。 为了简单起见,我们将使用 MNIST 数据集作为第一组示例。 然后,自编码器将根据输入数据生成潜向量,并使用解码器恢复输入。 在该第一示例中,潜向量是 16 维。 - -首先,我们将通过构建编码器来实现自编码器。 - -“列表 3.2.1”显示了将 MNIST 数字压缩为 16 维潜在向量的编码器。 编码器是两个`Conv2D`的栈。 最后阶段是具有 16 个单元的`Dense`层,以生成潜向量。 - -“列表 3.2.1”:`autoencoder-mnist-3.2.1.py` - -```py -from tensorflow.keras.layers import Dense, Input -from tensorflow.keras.layers import Conv2D, Flatten -from tensorflow.keras.layers import Reshape, Conv2DTranspose -from tensorflow.keras.models import Model -from tensorflow.keras.datasets import mnist -from tensorflow.keras.utils import plot_model -from tensorflow.keras import backend as K -``` - -```py -import numpy as np -import matplotlib.pyplot as plt -``` - -```py -# load MNIST dataset -(x_train, _), (x_test, _) = mnist.load_data() -# reshape to (28, 28, 1) and normalize input images -image_size = x_train.shape[1] -x_train = np.reshape(x_train, [-1, image_size, image_size, 1]) -x_test = np.reshape(x_test, [-1, image_size, image_size, 1]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -input_shape = (image_size, image_size, 1) -batch_size = 32 -kernel_size = 3 -latent_dim = 16 -# encoder/decoder number of CNN layers and filters per layer -layer_filters = [32, 64] -# build the autoencoder model -# first build the encoder model -inputs = Input(shape=input_shape, name='encoder_input') -x = inputs -# stack of Conv2D(32)-Conv2D(64) -for filters in layer_filters: - x = Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu', - strides=2, - padding='same')(x) -``` - -```py -# shape info needed to build decoder model -# so we don't do hand computation -# the input to the decoder's first -# Conv2DTranspose will have this shape -# shape is (7, 7, 64) which is processed by -# the decoder back to (28, 28, 1) -shape = K.int_shape(x) -``` - -```py -# generate latent vector -x = Flatten()(x) -latent = Dense(latent_dim, name='latent_vector')(x) -``` - -```py -# instantiate encoder model -encoder = Model(inputs, - latent, - name='encoder') -encoder.summary() -plot_model(encoder, - to_file='encoder.png', - show_shapes=True) -``` - -```py -# build the decoder model -latent_inputs = Input(shape=(latent_dim,), name='decoder_input') -# use the shape (7, 7, 64) that was earlier saved -x = Dense(shape[1] * shape[2] * shape[3])(latent_inputs) -# from vector to suitable shape for transposed conv -x = Reshape((shape[1], shape[2], shape[3]))(x) -``` - -```py -# stack of Conv2DTranspose(64)-Conv2DTranspose(32) -for filters in layer_filters[::-1]: - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - activation='relu', - strides=2, - padding='same')(x) -``` - -```py -# reconstruct the input -outputs = Conv2DTranspose(filters=1, - kernel_size=kernel_size, - activation='sigmoid', - padding='same', - name='decoder_output')(x) -``` - -```py -# instantiate decoder model -decoder = Model(latent_inputs, outputs, name='decoder') -decoder.summary() -plot_model(decoder, to_file='decoder.png', show_shapes=True) -``` - -```py -# autoencoder = encoder + decoder -# instantiate autoencoder model -autoencoder = Model(inputs, - decoder(encoder(inputs)), - name='autoencoder') -autoencoder.summary() -plot_model(autoencoder, - to_file='autoencoder.png', - show_shapes=True) -``` - -```py -# Mean Square Error (MSE) loss function, Adam optimizer -autoencoder.compile(loss='mse', optimizer='adam') -``` - -```py -# train the autoencoder -autoencoder.fit(x_train, - x_train, - validation_data=(x_test, x_test), - epochs=1, - batch_size=batch_size) -``` - -```py -# predict the autoencoder output from test data -x_decoded = autoencoder.predict(x_test) -``` - -```py -# display the 1st 8 test input and decoded images -imgs = np.concatenate([x_test[:8], x_decoded[:8]]) -imgs = imgs.reshape((4, 4, image_size, image_size)) -imgs = np.vstack([np.hstack(i) for i in imgs]) -plt.figure() -plt.axis('off') -plt.title('Input: 1st 2 rows, Decoded: last 2 rows') -plt.imshow(imgs, interpolation='none', cmap='gray') -plt.savefig('input_and_decoded.png') -plt.show() -``` - -“图 3.2.1”显示了`plot_model()`生成的架构模型图,与`encoder.summary()`生成的文本版本相同。 保存最后一个`Conv2D`的输出形状以计算解码器输入层的尺寸,以便轻松重建 MNIST 图像:`shape = K.int_shape(x)`。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_03_03.png) - -图 3.2.1:编码器模型由`Conv2D(32) - Conv2D(64) - Dense(16)`组成,以生成低维潜向量 - -列表 3.2.1 中的解码器对潜在向量进行解压缩,以恢复 MNIST 数字。 解码器输入级是`Dense`层,它将接受潜在向量。 单元的数量等于从编码器保存的`Conv2D`输出尺寸的乘积。 这样做是为了便于我们调整`Dense`层`Dense`层的输出大小,以最终恢复原始 MNIST 图像尺寸。 - -解码器由三个`Conv2DTranspose`的栈组成。 在我们的案例中,我们将使用**转置的 CNN**(有时称为**反卷积**),它是解码器中常用的。 我们可以将转置的 CNN(`Conv2DTranspose`)想象成 CNN 的逆过程。 - -在一个简单的示例中,如果 CNN 将图像转换为特征映射,则转置的 CNN 将生成给定特征映射的图像。“图 3.2.2”显示了解码器模型: - -![A screenshot of a cell phone Description automatically generated](img/B14853_03_04.png) - -图 3.2.2:解码器模型由`Dense(16) - Conv2DTranspose(64) - Conv2DTranspose(32) - Conv2DTranspose(1)`组成。 输入是经过解码以恢复原始输入的潜向量 - -通过将编码器和解码器连接在一起,我们可以构建自编码器。“图 3.2.3”说明了自编码器的模型图: - -![A screenshot of a cell phone Description automatically generated](img/B14853_03_05.png) - -图 3.2.3:通过将编码器模型和解码器模型结合在一起来构建自编码器模型。 此自编码器有 178 k 个参数 - -编码器的张量输出也是解码器的输入,该解码器生成自编码器的输出。 在此示例中,我们将使用 MSE 损失函数和 Adam 优化器。 在训练期间,输入与输出`x_train`相同。 我们应该注意,在我们的示例中,只有几层足以将验证损失在一个周期内驱动到 0.01。 对于更复杂的数据集,我们可能需要更深的编码器和解码器,以及更多的训练时间。 - -在对自编码器进行了一个周期的验证损失为 0.01 的训练之后,我们能够验证它是否可以对以前从未见过的 MNIST 数据进行编码和解码。“图 3.2.4”向我们展示了来自测试数据和相应解码图像的八个样本: - -![](img/B14853_03_06.png) - -图 3.2.4:根据测试数据预测自编码器。 前两行是原始输入测试数据。 最后两行是预测数据 - -除了图像中的轻微模糊之外,我们能够轻松识别出自编码器能够以良好的质量恢复输入。 随着我们训练更多的周期,结果将有所改善。 - -在这一点上,我们可能想知道:我们如何可视化空间中的潜在向量? 一种简单的可视化方法是强制自编码器使用 2 维潜在向量来学习 MNIST 数字特征。 从那里,我们可以将该潜在向量投影到二维空间上,以查看 MNIST 潜在向量的分布方式。“图 3.2.5”和“图 3.2.6”显示了 MNIST 数字的分布与潜在代码尺寸的关系。 - -![A close up of a mans face Description automatically generated](img/B14853_03_07.png) - -图 3.2.5:MNIST 数字分布与潜在代码尺寸`z[0]`和`z[1]`的关系。 原始照片可以在本书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter3-autoencoders/README.md)中找到。 - -在“图 3.2.5”中,我们可以看到特定数字的潜向量聚集在空间的某个区域上。 例如,数字 0 在左下象限中,而数字 1 在右上象限中。 这种群集在图中得到了反映。 实际上,同一图显示了导航或从潜在空间生成新数字的结果,如图“图 3.2.5”所示。 - -例如,从中心开始,向右上象限改变 2 维潜向量的值,这表明数字从 9 变为 1。这是可以预期的,因为从“图 3.2.5”开始,我们可以看到数字 9 群集的潜在代码值在中心附近,数字 1 群集的潜在代码值在右上象限。 - -对于“图 3.2.5”和“图 3.2.6”,我们仅研究了每个潜在向量维在 -4.0 和 +4.0 之间的区域: - -![](img/B14853_03_08.png) - -图 3.2.6:导航 2 维潜在向量空间时生成的数字 - -从“图 3.2.5”中可以看出,潜在代码分布不是连续的。 理想情况下,应该看起来像一个圆圈,其中到处都有有效值。 由于这种不连续性,因此如果解码潜伏向量,则几乎不会产生任何可识别的数字。 - -“图 3.2.5”和“图 3.2.6”经过 20 个训练周期后生成。 通过设置`latent_dim = 2`修改了`autoencoder-mnist-3.2.1.py`代码。 `plot_ results()`函数将 MNIST 数字绘制为 2 维潜在向量的函数。 为了方便起见,该程序另存为`autoencoder-2dim-mnist-3.2.2.py`,其部分代码显示在“列表 3.2.2”中。 其余代码实际上类似于“列表 3.2.1”,在此不再显示。 - -“列表 3.2.2”:`autoencoder-2dim-mnist-3.2.2.py` - -```py -def plot_results(models, - data, - batch_size=32, - model_name="autoencoder_2dim"): - """Plots 2-dim latent values as scatter plot of digits - then, plot MNIST digits as function of 2-dim latent vector -``` - -```py - Arguments: - models (list): encoder and decoder models - data (list): test data and label - batch_size (int): prediction batch size - model_name (string): which model is using this function - """ -``` - -```py - encoder, decoder = models - x_test, y_test = data - xmin = ymin = -4 - xmax = ymax = +4 - os.makedirs(model_name, exist_ok=True) -``` - -```py - filename = os.path.join(model_name, "latent_2dim.png") - # display a 2D plot of the digit classes in the latent space - z = encoder.predict(x_test, - batch_size=batch_size) - plt.figure(figsize=(12, 10)) -``` - -```py - # axes x and y ranges - axes = plt.gca() - axes.set_xlim([xmin,xmax]) - axes.set_ylim([ymin,ymax]) -``` - -```py - # subsample to reduce density of points on the plot - z = z[0::2] - y_test = y_test[0::2] - plt.scatter(z[:, 0], z[:, 1], marker="") - for i, digit in enumerate(y_test): - axes.annotate(digit, (z[i, 0], z[i, 1])) - plt.xlabel("z[0]") - plt.ylabel("z[1]") - plt.savefig(filename) - plt.show() -``` - -```py - filename = os.path.join(model_name, "digits_over_latent.png") - # display a 30x30 2D manifold of the digits - n = 30 - digit_size = 28 - figure = np.zeros((digit_size * n, digit_size * n)) - # linearly spaced coordinates corresponding to the 2D plot - # of digit classes in the latent space - grid_x = np.linspace(xmin, xmax, n) - grid_y = np.linspace(ymin, ymax, n)[::-1] -``` - -```py - for i, yi in enumerate(grid_y): - for j, xi in enumerate(grid_x): - z = np.array([[xi, yi]]) - x_decoded = decoder.predict(z) - 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 -``` - -```py - plt.figure(figsize=(10, 10)) - start_range = digit_size // 2 - end_range = n * digit_size + start_range + 1 - pixel_range = np.arange(start_range, end_range, digit_size) - sample_range_x = np.round(grid_x, 1) - sample_range_y = np.round(grid_y, 1) - plt.xticks(pixel_range, sample_range_x) - plt.yticks(pixel_range, sample_range_y) - plt.xlabel("z[0]") - plt.ylabel("z[1]") - plt.imshow(figure, cmap='Greys_r') - plt.savefig(filename) - plt.show() -``` - -这样就完成了和自编码器的检查。 接下来的章节将重点介绍其实际应用。 我们将从去噪自编码器开始。 - -# 3\. 去噪自编码器(DAE) - -现在,我们将构建具有实际应用的自编码器。 首先,让我们画一幅画,然后想象 MNIST 的数字图像被噪声破坏了,从而使人类更难以阅读。 我们能够构建一个去噪自编码器(DAE),以消除这些图像中的噪声。“图 3.3.1”向我们展示了三组 MNIST 数字。 每组的顶部行(例如,MNIST 数字 7、2、1、9、0、6、3、4 和 9)是原始图像。 中间的行显示了 DAE 的输入,这些输入是被噪声破坏的原始图像。 作为人类,我们发现很难读取损坏的 MNIST 数字。 最后一行显示 DAE 的输出。 - -![](img/B14853_03_09.png) - -图 3.3.1:原始 MNIST 数字(顶部行),损坏的原始图像(中间行)和去噪图像(最后一行) - -如图“图 3.3.2”所示,去噪自编码器的结构实际上与我们在上一节中介绍的 MNIST 的自编码器相同。 - -![](img/B14853_03_10.png) - -图 3.3.2:去噪自编码器的输入是损坏的图像。 输出是干净或去噪的图像。 假定潜向量为 16 维 - -“图 3.3.2”中的输入定义为: - -`x = x_ori + noise`(公式 3.3.1) - -在该公式中,`x_ori`表示被*噪声*破坏的原始 MNIST 图像。 编码器的目的是发现如何产生潜向量`z`,这将使解码器能够恢复诸如 MSE,如下所示:`x_ori`通过最小化相异损失函数: - -![](img/B14853_03_018.png) (Equation 3.3.2) - -在此示例中,`m`是输出尺寸(例如,在 MNIST 中,`m = width × height × channels = 28 × 28 × 1 = 784`)。 `x_ori[i]`和`x_tilde[i]`分别是`x_ori`和`x_tilde`的元素。 - -为了实现 DAE,我们将需要对上一节中介绍的自编码器进行一些更改。 首先,训练输入数据应损坏的 MNIST 数字。 训练输出数据是原始的原始 MNIST 数字相同。 这就像告诉自编码器应校正的图像是什么,或要求它找出在图像损坏的情况下如何消除噪声。 最后,我们必须在损坏的 MNIST 测试数据上验证自编码器。 - -“图 3.3.2"左侧所示的 MNIST 数字 7 是实际损坏的图像输入。 右边的是经过训练的降噪自编码器的干净图像输出。 - -“列表 3.3.1”:`denoising-autoencoder-mnist-3.3.1.py` - -```py -from tensorflow.keras.layers import Dense, Input -from tensorflow.keras.layers import Conv2D, Flatten -from tensorflow.keras.layers import Reshape, Conv2DTranspose -from tensorflow.keras.models import Model -from tensorflow.keras import backend as K -from tensorflow.keras.datasets import mnist -import numpy as np -import matplotlib.pyplot as plt -from PIL import Image -``` - -```py -np.random.seed(1337) -``` - -```py -# load MNIST dataset -(x_train, _), (x_test, _) = mnist.load_data() -``` - -```py -# reshape to (28, 28, 1) and normalize input images -image_size = x_train.shape[1] -x_train = np.reshape(x_train, [-1, image_size, image_size, 1]) -x_test = np.reshape(x_test, [-1, image_size, image_size, 1]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# generate corrupted MNIST images by adding noise with normal dist -# centered at 0.5 and std=0.5 -noise = np.random.normal(loc=0.5, scale=0.5, size=x_train.shape) -x_train_noisy = x_train + noise -``` - -```py -noise = np.random.normal(loc=0.5, scale=0.5, size=x_test.shape) -x_test_noisy = x_test + noise -# adding noise may exceed normalized pixel values>1.0 or <0.0 -# clip pixel values >1.0 to 1.0 and <0.0 to 0.0 -x_train_noisy = np.clip(x_train_noisy, 0., 1.) -x_test_noisy = np.clip(x_test_noisy, 0., 1.) -# network parameters -input_shape = (image_size, image_size, 1) -batch_size = 32 -kernel_size = 3 -latent_dim = 16 -# encoder/decoder number of CNN layers and filters per layer -layer_filters = [32, 64] -``` - -```py -# build the autoencoder model -# first build the encoder model -inputs = Input(shape=input_shape, name='encoder_input') -x = inputs -``` - -```py -# stack of Conv2D(32)-Conv2D(64) -for filters in layer_filters: - x = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=2, - activation='relu', - padding='same')(x) -``` - -```py -# shape info needed to build decoder model so we don't do hand computation -# the input to the decoder's first Conv2DTranspose will have this shape -# shape is (7, 7, 64) which can be processed by the decoder back to (28, 28, 1) -shape = K.int_shape(x) -``` - -```py -# generate the latent vector -x = Flatten()(x) -latent = Dense(latent_dim, name='latent_vector')(x) -``` - -```py -# instantiate encoder model -encoder = Model(inputs, latent, name='encoder') -encoder.summary() -``` - -```py -# build the decoder model -latent_inputs = Input(shape=(latent_dim,), name='decoder_input') -# use the shape (7, 7, 64) that was earlier saved -x = Dense(shape[1] * shape[2] * shape[3])(latent_inputs) -# from vector to suitable shape for transposed conv -x = Reshape((shape[1], shape[2], shape[3]))(x) -``` - -```py -# stack of Conv2DTranspose(64)-Conv2DTranspose(32) -for filters in layer_filters[::-1]: - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=2, - activation='relu', - padding='same')(x) -``` - -```py -# reconstruct the denoised input -outputs = Conv2DTranspose(filters=1, - kernel_size=kernel_size, - padding='same', - activation='sigmoid', - name='decoder_output')(x) -``` - -```py -# instantiate decoder model -decoder = Model(latent_inputs, outputs, name='decoder') -decoder.summary() -``` - -```py -# autoencoder = encoder + decoder -# instantiate autoencoder model -autoencoder = Model(inputs, decoder(encoder(inputs)), name='autoencoder') -autoencoder.summary() -``` - -```py -# Mean Square Error (MSE) loss function, Adam optimizer -autoencoder.compile(loss='mse', optimizer='adam') -``` - -```py -# train the autoencoder -autoencoder.fit(x_train_noisy, - x_train, - validation_data=(x_test_noisy, x_test), - epochs=10, - batch_size=batch_size) -``` - -```py -# predict the autoencoder output from corrupted test images -x_decoded = autoencoder.predict(x_test_noisy) -``` - -```py -# 3 sets of images with 9 MNIST digits -# 1st rows - original images -# 2nd rows - images corrupted by noise -# 3rd rows - denoised images -rows, cols = 3, 9 -num = rows * cols -imgs = np.concatenate([x_test[:num], x_test_noisy[:num], x_decoded[:num]]) -imgs = imgs.reshape((rows * 3, cols, image_size, image_size)) -imgs = np.vstack(np.split(imgs, rows, axis=1)) -imgs = imgs.reshape((rows * 3, -1, image_size, image_size)) -imgs = np.vstack([np.hstack(i) for i in imgs]) -imgs = (imgs * 255).astype(np.uint8) -plt.figure() -plt.axis('off') -plt.title('Original images: top rows, ' - 'Corrupted Input: middle rows, ' - 'Denoised Input: third rows') -plt.imshow(imgs, interpolation='none', cmap='gray') -Image.fromarray(imgs).save('corrupted_and_denoised.png') -plt.show() -``` - -“列表 3.3.1”显示了去噪自编码器,该编码器已添加到官方 Keras GitHub 存储库中。 使用相同的 MNIST 数据集,我们可以通过添加随机噪声来模拟损坏的图像。 添加的噪声是高斯分布,平均值为`μ = 0.5`,标准差为`σ = 0.5`。 由于添加随机噪声可能会将像素数据推入小于 0 或大于 1 的无效值,因此像素值会被裁剪为`[0.1, 1.0]`范围。 - -其他所有内容实际上都与上一节中的自编码器相同。 我们将使用相同的 MSE 损失函数和 Adam 优化器。 但是,训练的周期数已增加到 10。这是为了进行足够的参数优化。 - -“图 3.3.3”显示了 DAE 在某种程度上的鲁棒性,因为噪声级别从`σ = 0.5`增至`σ = 0.75`和`σ = 1.0`。 在`σ = 0.75`处,DAE 仍能够恢复原始图像。 但是,在`σ = 1.0`处,一些数字,例如第二和第三组中的 4 和 5,将无法正确恢复。 - -![](img/B14853_03_11.png) - -图 3.3.3:降噪自编码器的表现随着噪声水平的提高而增加 - -我们已经完成去噪自编码器的讨论和实现。 尽管此概念已在 MNIST 数字上进行了演示,但该思想也适用于其他信号。 在下一节中,我们将介绍自编码器的另一种实际应用,称为着色自编码器。 - -# 4\. 自动着色自编码器 - -现在,我们将致力于自编码器的另一个实际应用。 在这种情况下,我们将想象一下,我们有一张灰度照片,并且想要构建一个可以自动为其添加颜色的工具。 我们要复制人类的能力,以识别海洋和天空为蓝色,草地和树木为绿色,云层为白色,依此类推。 - -如图“图 3.4.1”所示,如果给我们前景的稻田,背景的火山和顶部的天空的灰度照片(左),我们可以添加适当的颜色(右)。 - -![](img/B14853_03_12.png) - -图 3.4.1:为 Mayon 火山的灰度照片添加颜色。 着色网络应通过向灰度照片添加颜色来复制人类的能力。 左照片是灰度的。 正确的照片是彩色的。 原始彩色照片可以在本书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter3-autoencoders/README.md)中找到。 - -对于自编码器,一种简单的自动着色算法似乎是一个合适的问题。 如果我们可以使用足够数量的灰度照片作为输入并使用相应的彩色照片作为输出来训练自编码器,则可能会在正确应用颜色时发现隐藏的结构。 大致上,这是去噪的反向过程。 问题是,自编码器能否在原始灰度图像上添加颜色(良好的噪点)? - -“列表 3.4.1”显示了着色自编码器网络。 着色自编码器网络是我们用于 MNIST 数据集的降噪自编码器的修改版本。 首先,我们需要一个彩色照片的灰度数据集。 我们之前使用过的 CIFAR10 数据库进行了 50,000 次训练和 10,000 次测试,可以将`32×32` RGB 照片转换为灰度图像。 如下清单所示,我们可以使用`rgb2gray()`函数在 R,G 和 B 分量上应用权重,以从彩色转换为灰度: - -“列表 3.4.1”:`colorization-autoencoder-cifar10-3.4.1.py` - -```py -from tensorflow.keras.layers import Dense, Input -from tensorflow.keras.layers import Conv2D, Flatten -from tensorflow.keras.layers import Reshape, Conv2DTranspose -from tensorflow.keras.models import Model -from tensorflow.keras.callbacks import ReduceLROnPlateau -from tensorflow.keras.callbacks import ModelCheckpoint -from tensorflow.keras.datasets import cifar10 -from tensorflow.keras.utils import plot_model -from tensorflow.keras import backend as K -``` - -```py -import numpy as np -import matplotlib.pyplot as plt -import os -``` - -```py -def rgb2gray(rgb): - """Convert from color image (RGB) to grayscale. - Source: opencv.org - grayscale = 0.299*red + 0.587*green + 0.114*blue - Argument: - rgb (tensor): rgb image - Return: - (tensor): grayscale image - """ - return np.dot(rgb[...,:3], [0.299, 0.587, 0.114]) -``` - -```py -# load the CIFAR10 data -(x_train, _), (x_test, _) = cifar10.load_data() -``` - -```py -# input image dimensions -# we assume data format "channels_last" -img_rows = x_train.shape[1] -img_cols = x_train.shape[2] -channels = x_train.shape[3] -# create saved_images folder -imgs_dir = 'saved_images' -save_dir = os.path.join(os.getcwd(), imgs_dir) -if not os.path.isdir(save_dir): - os.makedirs(save_dir) -``` - -```py -# display the 1st 100 input images (color and gray) -imgs = x_test[:100] -imgs = imgs.reshape((10, 10, img_rows, img_cols, channels)) -imgs = np.vstack([np.hstack(i) for i in imgs]) -plt.figure() -plt.axis('off') -plt.title('Test color images (Ground Truth)') -plt.imshow(imgs, interpolation='none') -plt.savefig('%s/test_color.png' % imgs_dir) -plt.show() -``` - -```py -# convert color train and test images to gray -x_train_gray = rgb2gray(x_train) -x_test_gray = rgb2gray(x_test) -``` - -```py -# display grayscale version of test images -imgs = x_test_gray[:100] -imgs = imgs.reshape((10, 10, img_rows, img_cols)) -imgs = np.vstack([np.hstack(i) for i in imgs]) -plt.figure() -plt.axis('off') -plt.title('Test gray images (Input)') -plt.imshow(imgs, interpolation='none', cmap='gray') -plt.savefig('%s/test_gray.png' % imgs_dir) -plt.show() -``` - -```py -# normalize output train and test color images -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# normalize input train and test grayscale images -x_train_gray = x_train_gray.astype('float32') / 255 -x_test_gray = x_test_gray.astype('float32') / 255 -``` - -```py -# reshape images to row x col x channel for CNN output/validation -x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, channels) -x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, channels) -``` - -```py -# reshape images to row x col x channel for CNN input -x_train_gray = x_train_gray.reshape(x_train_gray.shape[0], img_rows, img_cols, 1) -x_test_gray = x_test_gray.reshape(x_test_gray.shape[0], img_rows, img_cols, 1) -``` - -```py -# network parameters -input_shape = (img_rows, img_cols, 1) -batch_size = 32 -kernel_size = 3 -latent_dim = 256 -# encoder/decoder number of CNN layers and filters per layer -layer_filters = [64, 128, 256] -``` - -```py -# build the autoencoder model -# first build the encoder model -inputs = Input(shape=input_shape, name='encoder_input') -x = inputs -# stack of Conv2D(64)-Conv2D(128)-Conv2D(256) -for filters in layer_filters: - x = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=2, - activation='relu', - padding='same')(x) -``` - -```py -# shape info needed to build decoder model so we don't do hand computation -# the input to the decoder's first Conv2DTranspose will have this shape -# shape is (4, 4, 256) which is processed by the decoder back to (32, 32, 3) -shape = K.int_shape(x) -``` - -```py -# generate a latent vector -x = Flatten()(x) -latent = Dense(latent_dim, name='latent_vector')(x) -``` - -```py -# instantiate encoder model -encoder = Model(inputs, latent, name='encoder') -encoder.summary() -# build the decoder model -latent_inputs = Input(shape=(latent_dim,), name='decoder_input') -x = Dense(shape[1]*shape[2]*shape[3])(latent_inputs) -x = Reshape((shape[1], shape[2], shape[3]))(x) -``` - -```py -# stack of Conv2DTranspose(256)-Conv2DTranspose(128)-Conv2DTranspose(64) -for filters in layer_filters[::-1]: - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=2, - activation='relu', - padding='same')(x) -``` - -```py -outputs = Conv2DTranspose(filters=channels, - kernel_size=kernel_size, - activation='sigmoid', - padding='same', - name='decoder_output')(x) -``` - -```py -# instantiate decoder model -decoder = Model(latent_inputs, outputs, name='decoder') -decoder.summary() -# autoencoder = encoder + decoder -# instantiate autoencoder model -autoencoder = Model(inputs, decoder(encoder(inputs)), name='autoencoder') -autoencoder.summary() -``` - -```py -# prepare model saving directory. -save_dir = os.path.join(os.getcwd(), 'saved_models') -model_name = 'colorized_ae_model.{epoch:03d}.h5' -if not os.path.isdir(save_dir): - os.makedirs(save_dir) -filepath = os.path.join(save_dir, model_name) -``` - -```py -# reduce learning rate by sqrt(0.1) if the loss does not improve in 5 epochs -lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1), - cooldown=0, - patience=5, - verbose=1, - min_lr=0.5e-6) -# save weights for future use (e.g. reload parameters w/o training) -checkpoint = ModelCheckpoint(filepath=filepath, - monitor='val_loss', - verbose=1, - save_best_only=True) -``` - -```py -# Mean Square Error (MSE) loss function, Adam optimizer -autoencoder.compile(loss='mse', optimizer='adam') -``` - -```py -# called every epoch -callbacks = [lr_reducer, checkpoint] -``` - -```py -# train the autoencoder -autoencoder.fit(x_train_gray, - x_train, - validation_data=(x_test_gray, x_test), - epochs=30, - batch_size=batch_size, - callbacks=callbacks) -# predict the autoencoder output from test data -x_decoded = autoencoder.predict(x_test_gray) -``` - -```py -# display the 1st 100 colorized images -imgs = x_decoded[:100] -imgs = imgs.reshape((10, 10, img_rows, img_cols, channels)) -imgs = np.vstack([np.hstack(i) for i in imgs]) -plt.figure() -plt.axis('off') -plt.title('Colorized test images (Predicted)') -plt.imshow(imgs, interpolation='none') -plt.savefig('%s/colorized.png' % imgs_dir) -plt.show() -``` - -通过添加更多卷积和转置卷积,我们提高了自编码器的容量。 我们还将每个 CNN 块的过滤器数量增加了一倍。 潜向量现在为 256 维,以增加其可以表示的显着属性的数量,如自编码器部分所述。 最后,输出过滤器的大小已增加到三倍,或等于预期的彩色输出的 RGB 中的通道数。 - -现在使用灰度作为输入,原始 RGB 图像作为输出来训练着色自编码器。 训练将花费更多的时间,并在验证损失没有改善的情况下使用学习率降低器来缩小学习率。 通过告诉`tf.keras fit()`函数中的 callbacks 参数调用`lr_reducer()`函数,可以轻松完成此操作。 - -“图 3.4.2”演示了来自 CIFAR10 测试数据集的灰度图像的着色。 - -![A picture containing photo, many, indoor, different Description automatically generated](img/B14853_03_13.png) - -图 3.4.2:使用自编码器将灰度自动转换为彩色图像。 CIFAR10 测试灰度输入图像(左)和预测的彩色图像(右)。 原始彩色照片可以在本书的 GitHub 存储库中找到,网址为 https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter3-autoencoders/README.md - -“图 3.4.3”将基本事实与着色自编码器预测进行了比较: - -![A picture containing photo, indoor Description automatically generated](img/B14853_03_14.png) - -图 3.4.3:地面真彩色图像与预测彩色图像的并排比较。 原始彩色照片可以在本书的 GitHub 存储库中找到,网址为 https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter3-autoencoders/README.md - -自编码器执行可接受的着色作业。 预计大海或天空为蓝色,动物的阴影为棕色,云为白色,依此类推。 - -有一些明显的错误预测,例如红色车辆变成蓝色或蓝色车辆变成红色,偶尔的绿色领域被误认为是蓝天,而黑暗或金色的天空被转换为蓝天。 - -这是关于自编码器的最后一部分。 在以下各章中,我们将重新讨论以一种或另一种形式进行编码和解码的概念。 表示学习的概念在深度学习中非常基础。 - -# 5\. 总结 - -在本章中,我们已经介绍了自编码器,它们是将输入数据压缩为低维表示形式的神经网络,以便有效地执行结构转换,例如降噪和着色。 我们为 GAN 和 VAE 的更高级主题奠定了基础,我们将在后面的章节中介绍它们。 我们已经演示了如何从两个构建模块模型(编码器和解码器)实现自编码器。 我们还学习了如何提取输入分布的隐藏结构是 AI 的常见任务之一。 - -一旦学习了潜在代码,就可以对原始输入分布执行许多结构操作。 为了更好地了解输入分布,可以使用低级嵌入(类似于本章内容)或通过更复杂的降维技术(例如 t-SNE 或 PCA)来可视化潜在向量形式的隐藏结构。 - -除了去噪和着色外,自编码器还用于将输入分布转换为低维潜向量,可以针对其他任务(例如,分割,检测,跟踪,重建和视觉理解)进一步对其进行处理。 在“第 8 章”,“变分自编码器(VAE)”中,我们将讨论 VAE,它们在结构上与自编码器相同,但具有可解释的潜在代码,这些代码可以产生连续的潜在向量投影,因此有所不同。 - -在下一章中,我们将着手介绍 AI 最近最重要的突破之一,即 GAN。 在下一章中,我们将学习 GAN 的核心优势,即其综合看起来真实的数据的能力。 - -# 6\. 参考 - -1. `Ian Goodfellow et al.: Deep Learning. Vol. 1. Cambridge: MIT press, 2016 (http://www.deeplearningbook.org/).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/04.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/04.md deleted file mode 100644 index c4c08c1a..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/04.md +++ /dev/null @@ -1,784 +0,0 @@ -# 四、生成对抗网络(GAN) - -在本章中,我们将研究**生成对抗网络**(**GAN**)[1]。 GAN 属于生成模型家族。 但是,与自编码器不同,生成模型能够在给定任意编码的情况下创建新的有意义的输出。 - -在本章中,将讨论 GAN 的工作原理。 我们还将使用`tf.keras`回顾几个早期 GAN 的实现,而在本章的后面,我们将演示实现稳定训练所需的技术。 本章的范围涵盖了 GAN 实现的两个流行示例,**深度卷积 GAN**(**DCGAN**)[2]和**条件 GAN**(**CGAN**)[3]。 - -总之,本章的目标是: - -* GAN 的原理简介 -* GAN 的早期工作实现之一的简介,称为 DCGAN -* 改进的 DCGAN,称为 CGAN,它使用条件 -* 在`tf.keras`中实现 DCGAN 和 CGAN - -让我们从 GAN 的概述开始。 - -# 1\. GAN 概述 - -在进入 GAN 的更高级概念之前,让我们开始研究 GAN,并介绍它们背后的基本概念。 GAN 非常强大。 通过执行潜在空间插值,他们可以生成不是真实人的新人脸这一事实证明了这一简单的陈述。 - -可以在以下 YouTube 视频中看到 GAN 的高级功能: - -* [Progressive GAN [4]](https://youtu.be/G06dEcZ-QTg) -* [StyleGAN v1 [5]](https://youtu.be/kSLJriaOumA) -* [StyleGAN v2 [6]](https://youtu.be/c-NJtV9Jvp0) - -展示如何利用 GAN 产生逼真的面部的视频演示了它们的功能。 这个主题比我们之前看过的任何内容都先进得多。 例如,上面的视频演示了自编码器无法轻松完成的事情,我们在“第 3 章”,“自编码器”中介绍了这些内容。 - -GAN 可以通过训练两个相互竞争(且相互配合)的网络(称为**生成器**和**判别器**(有时称为**评论家**)。 生成器的作用是继续弄清楚如何生成伪造数据或信号(包括音频和图像),使伪造者蒙上阴影。 同时,判别器被训练以区分假信号和真实信号。 随着训练的进行,判别器将不再能够看到合成生成的数据与真实数据之间的差异。 从那里,可以丢弃判别器,然后可以使用生成器来创建从未见过的新的真实数据。 - -GAN 的基本概念很简单。 但是,我们将发现的一件事是,最具挑战性的问题是我们如何实现对生成器-判别器网络的稳定训练? 为了使两个网络都能同时学习,生成器和判别器之间必须存在健康的竞争。 由于损失函数是根据判别器的输出计算得出的,因此其参数会快速更新。 当判别器收敛速度更快时,生成器不再为其参数接收到足够的梯度更新,并且无法收敛。 除了难以训练之外,GAN 还可能遭受部分或全部模态崩溃的影响,这种情况下,生成器针对不同的潜在编码生成几乎相似的输出。 - -## GAN 的原理 - -如图“图 4.1.1”所示,GAN 类似于伪造者(生成器)-警察(判别器)场景。 在学院里,警察被教导如何确定美钞是真钞还是假钞。 来自银行的真实美钞样本和来自伪造者的伪钞样本被用来训练警察。 但是,伪造者会不时地假装他印制了真实的美元钞票。 最初,警方不会上当,并且会告诉造假者这笔钱是假的。 考虑到此反馈,造假者再次磨练他的技能,并尝试制作新的假美元钞票。 如预期的那样,警察将能够发现这笔钱是伪造的,并说明为什么美元钞票是伪造的: - -![A picture containing toy Description automatically generated](img/B14853_04_01.png) - -图 4.1.1:GAN 的生成器和判别器类似于伪造者和警察。 造假者的目的是欺骗警察,使他们相信美元钞票是真实的 - -此过程无限期地继续,但是到了造假者已经掌握了伪造货币的程度,以至于伪造品与真实货币几乎无法区分-甚至对于最受执业的警察也是如此。 然后,伪造者可以无限次打印美元钞票,而不会被警方抓获,因为它们不再可识别为伪造的。 - -如图“图 4.1.2”所示,GAN 由两个网络组成,一个生成器和一个判别器: - -![A close up of a logo Description automatically generated](img/B14853_04_02.png) - -图 4.1.2:GAN 由两个网络组成,一个生成器和一个判别器。 判别器经过训练,可以区分真实信号和虚假信号或数据。 生成器的工作是生成伪造的信号或数据,这些伪造的信号或数据最终会欺骗判别器 - -生成器的输入是噪声,输出是合成数据。 同时,判别器的输入将是实数据或合成数据。 真实数据来自真实的采样数据,而虚假数据来自生成器。 所有有效数据均标记为 1.0(即 100% 为真实概率),而所有合成数据均标记为 0.0(即 0% 为真实概率)。 由于标记过程是自动化的,因此 GAN 仍被认为是深度学习中无监督学习方法的一部分。 - -判别器的目标是从此提供的数据集中学习如何区分真实数据与伪数据。 在 GAN 训练的这一部分中,仅判别器参数将被更新。 像典型的二元分类器一样,判别器经过训练,可以在 0.0 到 1.0 的范围内预测置信度值,以了解给定输入数据与真实数据的接近程度。 但是,这只是故事的一半。 - -生成器将以固定的时间间隔假装其输出是真实数据,并要求 GAN 将其标记为 1.0。 然后,当将伪造数据提供给判别器时,自然会将其分类为伪造,标签接近 0.0。 - -优化器根据显示的标签(即 1.0)计算生成器参数更新。 在对新数据进行训练时,它还会考虑自己的预测。 换句话说,判别器对其预测有一些疑问,因此,GAN 将其考虑在内。 这次,GAN 将让梯度从判别器的最后一层向下向下传播到生成器的第一层。 但是,在大多数实践中,在训练的此阶段,判别器参数会暂时冻结。 生成器将使用梯度来更新其参数并提高其合成伪数据的能力。 - -总体而言,整个过程类似于两个网络相互竞争,同时仍在合作。 当 GAN 训练收敛时,最终结果是生成器,可以合成看似真实的数据。 判别器认为该合成数据是真实数据或带有接近 1.0 的标签,这意味着该判别器可以被丢弃。 生成器部分将有助于从任意噪声输入中产生有意义的输出。 - -下面的“图 4.1.3”中概述了该过程: - -![](img/B14853_04_03.png) - -图 4.1.3:训练判别器类似于使用二进制交叉熵损失训练二分类器网络。 伪数据由生成器提供,而真实数据来自真实样本 - -如上图所示,可以通过最小化以下等式中的损失函数来训练判别器: - -![](img/B14853_04_001.png) (Equation 4.1.1) - -该方程只是标准的二进制交叉熵代价函数。 损失是正确识别真实数据`1 - D(g(z))`的期望值与 1.0 正确识别合成数据`1 - D(g(z))`的期望值之和。 日志不会更改本地最小值的位置。 - -训练过程中将两个小批数据提供给判别器: - -1. `x`,来自采样数据的实数据(换言之,`x ~ p_data`),标签为 1.0 - -1. `x' = g(z)`,来自生成器的带有标签 0.0 的伪造数据 - -为了使的损失函数最小,将通过反向传播通过正确识别真实数据`D(x)`和合成数据`1 - D(g(z))`来更新判别器参数`θ^(D)`。 正确识别真实数据等同于`D(x) -> 1.0`,而正确分类伪造数据则与`D(g(z)) -> 0.0`或`1 - D(g(z)) -> 1.0`相同。 在此等式中,`z`是生成器用来合成新信号的任意编码或噪声向量。 两者都有助于最小化损失函数。 - -为了训练生成器,GAN 将判别器和生成器损失的总和视为零和博弈。 生成器损失函数只是判别器损失函数的负数: - -![](img/B14853_04_012.png) (Equation 4.1.2) - -然后可以将其更恰当地重写为值函数: - -![](img/B14853_04_013.png) (Equation 4.1.3) - -从生成器的角度来看,应将“公式 4.1.3”最小化。 从判别器的角度来看,值函数应最大化。 因此,生成器训练准则可以写成极大极小问题: - -![](img/B14853_04_014.png) (Equation 4.1.4) - -有时,我们会假装合成数据是带有标签 1.0 的真实数据,以此来欺骗判别器。 通过最大化`θ^(D)`,优化器将梯度更新发送到判别器参数,以将该合成数据视为真实数据。 同时,通过将`θ^(G)`的相关性减至最小,优化器将在上训练生成器的参数,从而欺骗识别器。 但是,实际上,判别器对将合成数据分类为伪造的预测很有信心,并且不会更新 GAN 参数。 此外,梯度更新很小,并且在传播到生成器层时已大大减小。 结果,生成器无法收敛。 - -![](img/B14853_04_04.png) - -图 4.1.4:训练生成器就像使用二进制交叉熵损失函数训练网络一样。 来自生成器的虚假数据显示为真实数据 - -解决方案是按以下形式重新构造生成器的损失函数: - -![](img/B14853_04_017.png) (Equation 4.1.5) - -损失函数只是通过训练生成器,最大程度地提高了判别器认为合成数据是真实数据的机会。 新公式不再是零和,而是纯粹由启发式驱动的。“图 4.1.4”显示了训练过程中的生成器。 在此图中,仅在训练整个对抗网络时才更新生成器参数。 这是因为梯度从判别器向下传递到生成器。 但是,实际上,判别器权重仅在对抗训练期间临时冻结。 - -在深度学习中,可以使用合适的神经网络架构来实现生成器和判别器。 如果数据或信号是图像,则生成器和判别器网络都将使用 CNN。 对于诸如音频之类的一维序列,两个网络通常都是循环的(RNN,LSTM 或 GRU)。 - -在本节中,我们了解到 GAN 的原理很简单。 我们还了解了如何通过熟悉的网络层实现 GAN。 GAN 与其他网络的区别在于众所周知,它们很难训练。 只需稍作更改,就可以使网络变得不稳定。 在以下部分中,我们将研究使用深度 CNN 的 GAN 早期成功实现之一。 它称为 DCGAN [3]。 - -# 2\. 在 Keras 中实现 DCGAN - -“图 4.2.1”显示 DCGAN,其中用于生成伪造的 MNIST 图像: - -![](img/B14853_04_05.png) - -图 4.2.1:DCGAN 模型 - -DCGAN 实现以下设计原则: - -* 使用`stride > 1`和卷积代替`MaxPooling2D`或`UpSampling2D`。 通过`stride > 1`,CNN 可以学习如何调整特征映射的大小。 -* 避免使用`Dense`层。 在所有层中使用 CNN。 `Dense`层仅用作生成器的第一层以接受`z`向量。 调整`Dense`层的输出大小,并成为后续 C​​NN 层的输入。 -* 使用**批量归一化**(**BN**),通过将每一层的输入归一化以使均值和单位方差为零,来稳定学习。 生成器输出层和判别器输入层中没有 BN。 在此处要介绍的实现示例中,没有在标识符中使用批量归一化。 -* **整流线性单元**(**ReLU**)在生成器的所有层中均使用,但在输出层中则使用`tanh`激活。 在此处要介绍的实现示例中,在生成器的输出中使用`sigmoid`代替`tanh`,因为通常会导致对 MNIST 数字进行更稳定的训练。 -* 在判别器的所有层中使用 **Leaky ReLU**。 与 ReLU 不同,Leaky ReLU 不会在输入小于零时将所有输出清零,而是生成一个等于`alpha x input`的小梯度。 在以下示例中,`alpha = 0.2`。 - -生成器学习从 100 维输入向量(`[-1.0,1.0]`范围内具有均匀分布的 100 维随机噪声)生成伪图像。 判别器将真实图像与伪图像分类,但是在训练对抗网络时无意中指导生成器如何生成真实图像。 在我们的 DCGAN 实现中使用的核大小为 5。这是为了允许它增加卷积的接收场大小和表达能力。 - -生成器接受由 -1.0 到 1.0 范围内的均匀分布生成的 100 维`z`向量。 生成器的第一层是`7 x 7 x 128 = 6,272`单元的密集层。 基于输出图像的预期最终尺寸(`28 x 28 x 1`,28 是 7 的倍数)和第一个`Conv2DTranspose`的过滤器数量(等于 128)来计算单元数量。 - -我们可以将转置的 CNN(`Conv2DTranspose`)想象成 CNN 的逆过程。 在一个简单的示例中,如果 CNN 将图像转换为特征映射,则转置的 CNN 将生成给定特征映射的图像。 因此,转置的 CNN 在上一章的解码器中和本章的生成器中使用。 - -在对`strides = 2`进行两个`Conv2DTranspose`之后,特征映射的大小将为`28 x 28 x n_filter`。 每个`Conv2DTranspose`之前都有批量规范化和 ReLU。 最后一层具有 *Sigmoid* 激活,可生成`28 x 28 x 1`假 MNIST 图像。 将每个像素标准化为与`[0, 255]`灰度级相对应的`[0.0, 1.0]`。 下面的“列表 4.2.1”显示了`tf.keras`中生成器网络的实现。 定义了一个函数来生成生成器模型。 由于整个代码的长度,我们将列表限制为正在讨论的特定行。 - -[完整的代码可在 GitHub 上获得](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -“列表 4.2.1”:`dcgan-mnist-4.2.1.py` - -```py -def build_generator(inputs, image_size): - """Build a Generator Model -``` - -```py - Stack of BN-ReLU-Conv2DTranpose to generate fake images - Output activation is sigmoid instead of tanh in [1]. - Sigmoid converges easily. -``` - -```py - Arguments: - inputs (Layer): Input layer of the generator - the z-vector) - image_size (tensor): Target size of one side - (assuming square image) -``` - -```py - Returns: - generator (Model): Generator Model - """ -``` - -```py - image_resize = image_size // 4 - # network parameters - kernel_size = 5 - layer_filters = [128, 64, 32, 1] -``` - -```py - x = Dense(image_resize * image_resize * layer_filters[0])(inputs) - x = Reshape((image_resize, image_resize, layer_filters[0]))(x) -``` - -```py - for filters in layer_filters: - # first two convolution layers use strides = 2 - # the last two use strides = 1 - if filters > layer_filters[-2]: - strides = 2 - else: - strides = 1 - x = BatchNormalization()(x) - x = Activation('relu')(x) - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - x = Activation('sigmoid')(x) - generator = Model(inputs, x, name='generator') - return generator -``` - -判别器与相似,是许多基于 CNN 的分类器。 输入是`28 x 28 x 1`MNIST 图像,分类为真实(1.0)或伪(0.0)。 有四个 CNN 层。 除了最后的卷积,每个`Conv2D`都使用`strides = 2`将特征映射下采样两个。 然后每个`Conv2D`之前都有一个泄漏的 ReLU 层。 最终的过滤器大小为 256,而初始的过滤器大小为 32,并使每个卷积层加倍。 最终的过滤器大小 128 也适用。 但是,我们会发现生成的图像在 256 的情况下看起来更好。最终输出层被展平,并且在通过 Sigmoid 激活层缩放后,单个单元`Dense`层在 0.0 到 1.0 之间生成预测。 输出被建模为伯努利分布。 因此,使用了二进制交叉熵损失函数。 - -建立生成器和判别器模型后,通过将生成器和判别器网络连接起来,建立对抗模型。 鉴别网络和对抗网络都使用 RMSprop 优化器。 判别器的学习率是`2e-4`,而对抗网络的学习率是`1e-4`。 判别器的 RMSprop 衰减率为`6e-8`,对抗网络的 RMSprop 衰减率为`3e-8`。 - -将对手的学习率设置为判别器的一半将使训练更加稳定。 您会从“图 4.1.3”和“图 4.1.4”中回忆起,GAN 训练包含两个部分:判别器训练和生成器训练,这是冻结判别器权重的对抗训练。 - -“列表 4.2.2”显示了`tf.keras`中判别器的实现。 定义一个函数来建立鉴别模型。 - -“列表 4.2.2”:`dcgan-mnist-4.2.1.py` - -```py -def build_discriminator(inputs): - """Build a Discriminator Model -``` - -```py - Stack of LeakyReLU-Conv2D to discriminate real from fake. - The network does not converge with BN so it is not used here - unlike in [1] or original paper. -``` - -```py - Arguments: - inputs (Layer): Input layer of the discriminator (the image) -``` - -```py - Returns: - discriminator (Model): Discriminator Model - """ - kernel_size = 5 - layer_filters = [32, 64, 128, 256] -``` - -```py - x = inputs - for filters in layer_filters: - # first 3 convolution layers use strides = 2 - # last one uses strides = 1 - if filters == layer_filters[-1]: - strides = 1 - else: - strides = 2 - x = LeakyReLU(alpha=0.2)(x) - x = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - x = Flatten()(x) - x = Dense(1)(x) - x = Activation('sigmoid')(x) - discriminator = Model(inputs, x, name='discriminator') - return discriminator -``` - -在“列表 4.2.3”中,我们将说明如何构建 GAN 模型。 首先,建立鉴别模型,然后实例化生成器模型。 对抗性模型只是生成器和判别器组合在一起。 在许多 GAN 中,批大小为 64 似乎是最常见的。 网络参数显示在“列表 4.2.3”中。 - -“列表 4.2.3”:`dcgan-mnist-4.2.1.py` - -建立 DCGAN 模型并调用训练例程的函数: - -```py -def build_and_train_models(): - # load MNIST dataset - (x_train, _), (_, _) = mnist.load_data() -``` - -```py - # reshape data for CNN as (28, 28, 1) and normalize - image_size = x_train.shape[1] - x_train = np.reshape(x_train, [-1, image_size, image_size, 1]) - x_train = x_train.astype('float32') / 255 -``` - -```py - model_name = "dcgan_mnist" - # network parameters - # the latent or z vector is 100-dim - latent_size = 100 - batch_size = 64 - train_steps = 40000 - lr = 2e-4 - decay = 6e-8 - input_shape = (image_size, image_size, 1) -``` - -```py - # build discriminator model - inputs = Input(shape=input_shape, name='discriminator_input') - discriminator = build_discriminator(inputs) - # [1] or original paper uses Adam, - # but discriminator converges easily with RMSprop - optimizer = RMSprop(lr=lr, decay=decay) - discriminator.compile(loss='binary_crossentropy', - optimizer=optimizer, - metrics=['accuracy']) - discriminator.summary() -``` - -```py - # build generator model - input_shape = (latent_size, ) - inputs = Input(shape=input_shape, name='z_input') - generator = build_generator(inputs, image_size) - generator.summary() -``` - -```py - # build adversarial model - optimizer = RMSprop(lr=lr * 0.5, decay=decay * 0.5) - # freeze the weights of discriminator during adversarial training - discriminator.trainable = False - # adversarial = generator + discriminator - adversarial = Model(inputs, - discriminator(generator(inputs)), - name=model_name) - adversarial.compile(loss='binary_crossentropy', - optimizer=optimizer, - metrics=['accuracy']) - adversarial.summary() -``` - -```py - # train discriminator and adversarial networks - models = (generator, discriminator, adversarial) - params = (batch_size, latent_size, train_steps, model_name) - train(models, x_train, params) -``` - -从“列表 4.2.1”和“列表 4.2.2”中可以看出,DCGAN 模型很简单。 使它们难以构建的原因是,网络中的较小更改设计很容易破坏训练收敛。 例如,如果在判别器中使用批量归一化,或者如果生成器中的`strides = 2`传输到后面的 C​​NN 层,则 DCGAN 将无法收敛。 - -“列表 4.2.4”显示了专用于训练判别器和对抗网络的函数。 由于自定义训练,将不使用常规的`fit()`函数。 取而代之的是,调用`train_on_batch()`对给定的数据批量运行单个梯度更新。 然后通过对抗网络训练生成器。 训练首先从数据集中随机选择一批真实图像。 这被标记为实数(1.0)。 然后,生成器将生成一批伪图像。 这被标记为假(0.0)。 这两个批量是连接在一起的,用于训练判别器。 - -完成此操作后,生成器将生成一批新的伪图像,并将其标记为真实(1.0)。 这批将用于训练对抗网络。 交替训练这两个网络约 40,000 步。 定期将基于特定噪声向量生成的 MNIST 数字保存在文件系统中。 在最后的训练步骤中,网络已收敛。 生成器模型也保存在文件中,因此我们可以轻松地将训练后的模型重新用于未来的 MNIST 数字生成。 但是,仅保存生成器模型,因为这是该 DCGAN 在生成新 MNIST 数字时的有用部分。 例如,我们可以通过执行以下操作来生成新的和随机的 MNIST 数字: - -```py -python3 dcgan-mnist-4.2.1.py --generator=dcgan_mnist.h5 -``` - -“列表 4.2.4”:`dcgan-mnist-4.2.1.py` - -训练判别器和对抗网络的函数: - -```py -def train(models, x_train, params): - """Train the Discriminator and Adversarial Networks -``` - -```py - Alternately train Discriminator and Adversarial networks by batch. - Discriminator is trained first with properly real and fake images. - Adversarial is trained next with fake images pretending to be real - Generate sample images per save_interval. -``` - -```py - Arguments: - models (list): Generator, Discriminator, Adversarial models - x_train (tensor): Train images - params (list) : Networks parameters -``` - -```py - """ - # the GAN component models - generator, discriminator, adversarial = models - # network parameters - batch_size, latent_size, train_steps, model_name = params - # the generator image is saved every 500 steps - save_interval = 500 - # noise vector to see how the generator output evolves during training - noise_input = np.random.uniform(-1.0, 1.0, size=[16, latent_size]) - # number of elements in train dataset - train_size = x_train.shape[0] - for i in range(train_steps): - # train the discriminator for 1 batch - # 1 batch of real (label=1.0) and fake images (label=0.0) - # randomly pick real images from dataset - rand_indexes = np.random.randint(0, train_size, size=batch_size) - real_images = x_train[rand_indexes] - # generate fake images from noise using generator - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # generate fake images - fake_images = generator.predict(noise) - # real + fake images = 1 batch of train data - x = np.concatenate((real_images, fake_images)) - # label real and fake images - # real images label is 1.0 - y = np.ones([2 * batch_size, 1]) - # fake images label is 0.0 - y[batch_size:, :] = 0.0 - # train discriminator network, log the loss and accuracy - loss, acc = discriminator.train_on_batch(x, y) - log = "%d: [discriminator loss: %f, acc: %f]" % (i, loss, acc) -``` - -```py - # train the adversarial network for 1 batch - # 1 batch of fake images with label=1.0 - # since the discriminator weights - # are frozen in adversarial network - # only the generator is trained - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # label fake images as real or 1.0 - y = np.ones([batch_size, 1]) - # train the adversarial network - # note that unlike in discriminator training, - # we do not save the fake images in a variable - # the fake images go to the discriminator input of the adversarial - # for classification - # log the loss and accuracy - loss, acc = adversarial.train_on_batch(noise, y) - log = "%s [adversarial loss: %f, acc: %f]" % (log, loss, acc) - print(log) - if (i + 1) % save_interval == 0: - # plot generator images on a periodic basis - plot_images(generator, - noise_input=noise_input, - show=False, - step=(i + 1), - model_name=model_name) -``` - -```py - # save the model after training the generator - # the trained generator can be reloaded for - # future MNIST digit generation - generator.save(model_name + ".h5") -``` - -“图 4.2.2”显示了生成器伪造图像根据训练步骤的演变。 生成器已经以 5,000 步的速度生成了可识别的图像。 非常像拥有一个知道如何绘制数字的智能体。 值得注意的是,某些数字从一种可识别的形式(例如,最后一行的第二列中的 8)变为另一种形式(例如,0)。 当训练收敛时,判别器损失接近 0.5,而对抗性损失接近 1.0,如下所示: - -```py -39997: [discriminator loss: 0.423329, acc: 0.796875] [adversarial loss: -0.819355, acc: 0.484375] -39998: [discriminator loss: 0.471747, acc: 0.773438] [adversarial loss: -1.570030, acc: 0.203125] -39999: [discriminator loss: 0.532917, acc: 0.742188] [adversarial loss: -0.824350, acc: 0.453125] -``` - -我们可以看到以下结果: - -![](img/B14853_04_06.png) - -图 4.2.2:DCGAN 生成器在不同训练步骤生成的伪造图像 - -在本节中,由 DCGAN 生成的伪造图像是随机的。 - -生成器无法控制哪个特定数字。 没有机制可以请求生成器提供特定的数字。 这个问题可以通过称为 CGAN [4]的 GAN 变体来解决,我们将在下一部分中进行讨论。 - -# 3\. Conditional GAN - -使用与上一节相同的 GAN ,会对生成器和判别器输入都施加一个条件。 条件是数字的一键向量形式。 这与要生成的图像(生成器)或分类为真实或伪造的图像(判别器)相关。 CGAN 模型显示在“图 4.3.1”中。 - -CGAN 与 DCGAN 相似,除了附加的单热向量输入。 对于生成器,单热标签在`Dense`层之前与潜向量连接在一起。 对于判别器,添加了新的`Dense`层。 新层用于处理单热向量并对其进行整形,以使其适合于与后续 CNN 层的另一个输入连接。 - -![](img/B14853_04_07.png) - -图 4.3.1:CGAN 模型与 DCGAN 相似,只不过是单热向量,用于调节生成器和判别器的输出 - -生成器学习从 100 维输入向量和指定位数生成伪图像。 判别器基于真实和伪图像及其对应的标签将真实图像与伪图像分类。 - -CGAN 的基础仍然与原始 GAN 原理相同,区别在于判别器和生成器的输入均以“一热”标签`y`为条件。 - -通过在“公式 4.1.1”和“公式 4.1.5”中合并此条件,判别器和生成器的损失函数在“公式 4.3.1”和“公式 4.3.2”中显示,分别为: - -![](img/B14853_04_018.png) (Equation 4.3.1) - -![](img/B14853_04_019.png) (Equation 4.3.2) - -给定“图 4.3.2”,将损失函数写为: - -![](img/B14853_04_020.png) (Equation 4.3.3) - -![](img/B14853_04_021.png) (Equation 4.3.4) - -判别器的新损失函数旨在最大程度地减少预测来自数据集的真实图像和来自生成器的假图像(给定单热点标签)的误差。“图 4.3.2”显示了如何训练判别器。 - -![](img/B14853_04_08.png) - -图 4.3.2:训练 CGAN 判别器类似于训练 GAN 判别器。 唯一的区别是,所生成的伪造品和数据集的真实图像均以其相应的“一键通”标签作为条件。 - -生成器的新损失函数可最大程度地减少对以指定的一幅热标签为条件的伪造图像进行鉴别的正确预测。 生成器学习如何在给定单热向量的情况下生成特定的 MNIST 数字,该数字可能使判别器蒙蔽。“图 4.3.3”显示了如何训练生成器。 - -![](img/B14853_04_09.png) - -图 4.3.3:通过对抗网络训练 CGAN 生成器类似于训练 GAN 生成器。 唯一的区别是,生成的伪造图像以“一热”标签为条件 - -“列表 4.3.1”突出显示了判别器模型中所需的微小更改。 该代码使用`Dense`层处理单热点向量,并将其与输入图像连接在一起。 修改了`Model`实例以用于图像和一键输入向量。 - -“列表 4.3.1”:`cgan-mnist-4.3.1.py` - -突出显示了 DCGAN 中所做的更改: - -```py -def build_discriminator(inputs, labels, image_size): - """Build a Discriminator Model -``` - -```py - Inputs are concatenated after Dense layer. - Stack of LeakyReLU-Conv2D to discriminate real from fake. - The network does not converge with BN so it is not used here - unlike in DCGAN paper. -``` - -```py - Arguments: - inputs (Layer): Input layer of the discriminator (the image) - labels (Layer): Input layer for one-hot vector to condition - the inputs - image_size: Target size of one side (assuming square image) - Returns: - discriminator (Model): Discriminator Model - """ - kernel_size = 5 - layer_filters = [32, 64, 128, 256] -``` - -```py - x = inputs -``` - -```py - y = Dense(image_size * image_size)(labels) - y = Reshape((image_size, image_size, 1))(y) - x = concatenate([x, y]) -``` - -```py - for filters in layer_filters: - # first 3 convolution layers use strides = 2 - # last one uses strides = 1 - if filters == layer_filters[-1]: - strides = 1 - else: - strides = 2 - x = LeakyReLU(alpha=0.2)(x) - x = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - x = Flatten()(x) - x = Dense(1)(x) - x = Activation('sigmoid')(x) - # input is conditioned by labels - discriminator = Model([inputs, labels], x, name='discriminator') - return discriminator -``` - -以下“列表 4.3.2”突出显示了代码更改,以在生成器生成器函数中合并条件化单热标签。 对于`z`向量和单热向量输入,修改了`Model`实例。 - -“列表 4.3.2”:`cgan-mnist-4.3.1.py` - -突出显示了 DCGAN 中所做的更改: - -```py -def build_generator(inputs, labels, image_size): - """Build a Generator Model - Inputs are concatenated before Dense layer. - Stack of BN-ReLU-Conv2DTranpose to generate fake images. - Output activation is sigmoid instead of tanh in orig DCGAN. - Sigmoid converges easily. -``` - -```py - Arguments: - inputs (Layer): Input layer of the generator (the z-vector) - labels (Layer): Input layer for one-hot vector to condition the inputs - image_size: Target size of one side (assuming square image) - Returns: - generator (Model): Generator Model - """ - image_resize = image_size // 4 - # network parameters - kernel_size = 5 - layer_filters = [128, 64, 32, 1] -``` - -```py - x = concatenate([inputs, labels], axis=1) - x = Dense(image_resize * image_resize * layer_filters[0])(x) - x = Reshape((image_resize, image_resize, layer_filters[0]))(x) -``` - -```py - for filters in layer_filters: - # first two convolution layers use strides = 2 - # the last two use strides = 1 - if filters > layer_filters[-2]: - strides = 2 - else: - strides = 1 - x = BatchNormalization()(x) - x = Activation('relu')(x) - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - x = Activation('sigmoid')(x) - # input is conditioned by labels - generator = Model([inputs, labels], x, name='generator') - return generator -``` - -“列表 4.3.3”突出显示了在`train()`函数中所做的更改,以适应判别器和生成器的条件一热向量。 首先对 CGAN 判别器进行训练,以一批真实和伪造的数据为条件,这些数据以其各自的热门标签为条件。 然后,在给定单热标签条件假冒数据为假的情况下,通过训练对抗网络来更新生成器参数。 与 DCGAN 相似,在对抗训练中,判别器权重被冻结。 - -“列表 4.3.3”:`cgan-mnist-4.3.1.py` - -着重介绍了 DCGAN 中所做的更改: - -```py -def train(models, data, params): - """Train the Discriminator and Adversarial Networks -``` - -```py - Alternately train Discriminator and Adversarial networks by batch. - Discriminator is trained first with properly labelled real and fake images. - Adversarial is trained next with fake images pretending to be real. - Discriminator inputs are conditioned by train labels for real images, - and random labels for fake images. - Adversarial inputs are conditioned by random labels. - Generate sample images per save_interval. -``` - -```py - Arguments: - models (list): Generator, Discriminator, Adversarial models - data (list): x_train, y_train data - params (list): Network parameters -``` - -```py - """ - # the GAN models - generator, discriminator, adversarial = models - # images and labels - x_train, y_train = data - # network parameters - batch_size, latent_size, train_steps, num_labels, model_name = params - # the generator image is saved every 500 steps - save_interval = 500 - # noise vector to see how the generator output evolves during training - noise_input = np.random.uniform(-1.0, 1.0, size=[16, latent_size]) - # one-hot label the noise will be conditioned to - noise_class = np.eye(num_labels)[np.arange(0, 16) % num_labels] - # number of elements in train dataset - train_size = x_train.shape[0] -``` - -```py - print(model_name, - "Labels for generated images: ", - np.argmax(noise_class, axis=1)) -``` - -```py - for i in range(train_steps): - # train the discriminator for 1 batch - # 1 batch of real (label=1.0) and fake images (label=0.0) - # randomly pick real images from dataset - rand_indexes = np.random.randint(0, train_size, size=batch_size) - real_images = x_train[rand_indexes] - # corresponding one-hot labels of real images - real_labels = y_train[rand_indexes] - # generate fake images from noise using generator - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) -``` - -```py - # assign random one-hot labels - fake_labels = np.eye(num_labels)[np.random.choice(num_labels,batch_size)] - # generate fake images conditioned on fake labels - fake_images = generator.predict([noise, fake_labels]) - # real + fake images = 1 batch of train data - x = np.concatenate((real_images, fake_images)) - # real + fake one-hot labels = 1 batch of train one-hot labels - labels = np.concatenate((real_labels, fake_labels)) - # label real and fake images - # real images label is 1.0 - y = np.ones([2 * batch_size, 1]) - # fake images label is 0.0 - y[batch_size:, :] = 0.0 - # train discriminator network, log the loss and accuracy - loss, acc = discriminator.train_on_batch([x, labels], y) - log = "%d: [discriminator loss: %f, acc: %f]" % (i, loss, acc) - # train the adversarial network for 1 batch - # 1 batch of fake images conditioned on fake 1-hot labels - # w/ label=1.0 - # since the discriminator weights are frozen in - # adversarial network only the generator is trained - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # assign random one-hot labels - fake_labels = np.eye(num_labels)[np.random.choice(num_labels,batch_size)] -``` - -```py - # label fake images as real or 1.0 - y = np.ones([batch_size, 1]) - # train the adversarial network - # note that unlike in discriminator training, - # we do not save the fake images in a variable - # the fake images go to the discriminator input of the adversarial - # for classification - # log the loss and accuracy - loss, acc = adversarial.train_on_batch([noise, fake_labels], y) - log = "%s [adversarial loss: %f, acc: %f]" % (log, loss, acc) - print(log) - if (i + 1) % save_interval == 0: - # plot generator images on a periodic basis - plot_images(generator, - noise_input=noise_input, - noise_class=noise_class, - show=False, - step=(i + 1), - model_name=model_name) -``` - -```py - # save the model after training the generator - # the trained generator can be reloaded for - # future MNIST digit generation - generator.save(model_name + ".h5") -``` - -“图 4.3.4”显示了当生成器被调整为产生带有以下标签的数字时生成的 MNIST 数字的演变: - -```py -[0 1 2 3 -4 5 6 7 -8 9 0 1 -2 3 4 5] -``` - -我们可以看到以下结果: - -![](img/B14853_04_10.png) - -图 4.3.4:使用标签`[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5]`对 CGAN 在不同训练步骤中生成的伪造图像 - -鼓励您运行经过训练的生成器模型,以查看新的合成 MNIST 数字图像: - -```py -python3 cgan-mnist-4.3.1.py --generator=cgan_mnist.h5 -``` - -或者,也可以请求要生成的特定数字(例如 8): - -```py -python3 cgan-mnist-4.3.1.py --generator=cgan_mnist.h5 --digit=8 -``` - -使用 CGAN,就像有一个智能体,我们可以要求绘制数字,类似于人类如何写数字。 与 DCGAN 相比,CGAN 的主要优势在于我们可以指定希望智能体绘制的数字。 - -# 4。结论 - -本章讨论了 GAN 的一般原理,以便为我们现在要讨论的更高级的主题奠定基础,包括改进的 GAN,解缠的表示 GAN 和跨域 GAN。 我们从了解 GAN 如何由两个网络(称为生成器和判别器)组成的这一章开始。 判别器的作用是区分真实信号和虚假信号。 生成器的目的是欺骗判别器。 生成器通常与判别器结合以形成对抗网络。 生成器是通过训练对抗网络来学习如何生成可欺骗判别器的虚假数据的。 - -我们还了解了 GAN 的构建方法,但众所周知,其操作起来非常困难。 提出了`tf.keras`中的两个示例实现。 DCGAN 证明了可以训练 GAN 使用深层 CNN 生成伪造图像。 伪造的图像是 MNIST 数字。 但是,DCGAN 生成器无法控制应绘制的特定数字。 CGAN 通过调节生成器以绘制特定数字来解决此问题。 该病是单热标签的形式。 如果我们要构建可以生成特定类数据的智能体,则 CGAN 很有用。 - -在下一章中,将介绍 DCGAN 和 CGAN 的改进。 特别是,重点将放在如何稳定 DCGAN 的训练以及如何提高 CGAN 的感知质量上。 这将通过引入新的损失函数和稍有不同的模型架构来完成。 - -# 5\. 参考 - -1. `Ian Goodfellow. NIPS 2016 Tutorial: Generative Adversarial Networks. arXiv preprint arXiv:1701.00160, 2016 (https://arxiv.org/pdf/1701.00160.pdf).` -1. `Alec Radford, Luke Metz, and Soumith Chintala. Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks. arXiv preprint arXiv:1511.06434, 2015 (https://arxiv.org/pdf/1511.06434.pdf).` -1. `Mehdi Mirza and Simon Osindero. Conditional Generative Adversarial Nets. arXiv preprint arXiv:1411.1784, 2014 (https://arxiv.org/pdf/1411.1784.pdf).` -1. `Tero Karras et al. Progressive Growing of GANs for Improved Quality, Stability, and Variation. ICLR, 2018 (https://arxiv.org/pdf/1710.10196.pdf).` -1. `Tero Karras, , Samuli Laine, and Timo Aila. A Style-Based Generator Architecture for Generative Adversarial Networks. Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2019.` -1. `Tero Karras et al. Analyzing and Improving the Image Quality of StyleGAN. 2019 (https://arxiv.org/abs/1912.04958).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/05.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/05.md deleted file mode 100644 index 1815e4e3..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/05.md +++ /dev/null @@ -1,1136 +0,0 @@ -# 五、改进的 GAN - -自 2014 年引入**生成对抗网络**(**GAN**)以来,其流行度迅速提高。 GAN 已被证明是有用的生成模型,可以合成看起来真实的新数据。 深度学习中的许多研究论文都遵循提出的措施来解决原始 GAN 的困难和局限性。 - -正如我们在前几章中讨论的那样,众所周知,GAN 很难训练,并且易于崩溃。 模式损失是一种情况,即使损失函数已经被优化,但生成器仍会产生看起来相同的输出。 在 MNIST 数字的情况下,模式折叠时,生成器可能只产生数字 4 和 9,因为看起来很相似。 **Wasserstein GAN**(**WGAN**)[2]解决了这些问题,认为只需替换基于 **Wasserstein** 的 GAN 损失函数就可以稳定的训练和避免模式崩溃,也称为**陆地移动距离**(**EMD**)。 - -但是,稳定性问题并不是 GAN 的唯一问题。 也越来越需要来提高所生成图像的感知质量。 **最小二乘 GAN**(**LSGAN**)[3]建议同时解决这两个问题。 基本前提是,在训练过程中,Sigmoid 交叉熵损失会导致梯度消失。 这导致较差的图像质量。 最小二乘损失不会导致梯度消失。 与原始 GAN 生成的图像相比,生成的生成图像具有更高的感知质量。 - -在上一章中,CGAN 介绍了一种调节生成器输出的方法。 例如,如果要获取数字 8,则可以在生成器的输入中包含条件标签。 受 CGAN 的启发,**辅助分类器 GAN**(**ACGAN**)[4]提出了一种改进的条件算法,可产生更好的感知质量和输出多样性。 - -总之,本章的目的是介绍: - -* WGAN 的理论描述 -* 对 LSGAN 原理的理解 -* 对 ACGAN 原理的理解 -* 改进的 GAN 的`tf.keras`实现 – WGAN,LSGAN 和 ACGAN - -让我们从讨论 WGAN 开始。 - -# 1\. Wasserstein GAN - -如前所述,众所周知,GAN 很难训练。 判别器和生成器这两个网络的相反目标很容易导致训练不稳定。 判别器尝试从真实数据中正确分类伪造数据。 同时,生成器将尽最大努力欺骗判别器。 如果判别器的学习速度比生成器快,则生成器参数将无法优化。 另一方面,如果判别器学习较慢,则梯度可能会在到达生成器之前消失。 在最坏的情况下,如果判别器无法收敛,则生成器将无法获得任何有用的反馈。 - -WGAN 认为 GAN 固有的不稳定性是由于它的损失函数引起的,该函数基于 **Jensen-Shannon**(**JS**)距离。 在 GAN 中,生成器的目的是学习如何将一种源分布(例如噪声)从转换为估计的目标分布(例如 MNIST 数字)。 使用 GAN 的原始公式,损失函数实际上是使目标分布与其估计值之间的距离最小。 问题是,对于某些分布对,没有平滑的路径可以最小化此 JS 距离。 因此,训练将无法收敛。 - -在以下部分中,我们将研究三个距离函数,并分析什么可以替代更适合 GAN 优化的 JS 距离函数。 - -## 距离函数 - -可以通过检查其损失函数来了解训练 GAN 的稳定性。 为了更好地理解 GAN 损失函数,我们将回顾两个概率分布之间的公共距离或散度函数。 - -我们关注的是用于真实数据分配的`p_data`与用于生成器数据分配的`p_g`之间的距离。 GAN 的目标是制造`p_g -> p_data`。“表 5.1.1”显示了散度函数。 - -在大多数个最大似然任务中,我们将使用 **Kullback-Leibler**(**KL**)散度,或`D[KL]`损失函数可以衡量我们的神经网络模型预测与真实分布函数之间的距离。 如“公式 5.1.1”所示,由于`D[KL](p_data || p_g) ≠ D[KL](p_g || p_data)`,所以`D[KL]`不对称。 - -**JS** 或`D[JS]`是基于`D[KL]`的差异。 但是,与`D[KL]`不同,`D[JS]`是对称的并且是有限的。 在本节中,我们将演示优化 GAN 损失函数等同于优化`D[JS]`: - -| **散度** | **表达式** | -| --- | --- | -| Kullback-Leibler(KL)“公式 5.1.1” | ![](img/B14853_05_003.png) | -| | ![](img/B14853_05_004.png) | -| *詹森·香农(JS)“公式 5.1.2” | ![](img/B14853_05_005.png) | -| 陆地移动距离(EMD)或 Wasserstein 1 “公式 5.1.3” | ![](img/B14853_05_006.png) | -| | 其中`Π(p_data, p_g)`是所有联合分布`γ(x, y)`的集合,其边际为`p_data`和`p_g`。 | - -表 5.1.1:两个概率分布函数`p_data`和`p_g`之间的散度函数 - -EMD 背后的想法是,它是`d = ||x - y||`传输多少质量`γ(x, y)`,为了让概率分布`p_data`匹配`p_g`的度量。 `γ(x, y)`是所有可能的联合分布`Π(p_data, p_g)`的空间中的联合分布。 `γ(x, y)`也被称为运输计划,以反映运输质量以匹配两个概率分布的策略。 给定两个概率分布,有许多可能的运输计划。 大致而言, `inf`表示成本最低的运输计划。 - -例如,“图 5.1.1”向我们展示了两个简单的离散分布`x`和`y`: - -![](img/B14853_05_01.png) - -图 5.1.1:EMD 是从`x`传输以匹配目标分布`y`的质量的加权数量。 - -在位置`i = 1, 2, 3, 4`上,`x`在具有质量`m[i], i = 1, 2, 3, 4`。同时,位置`y[i], i = 1, 2`上,`y`的质量为`m[i], i = 1, 2`。为了匹配分布`y`,图中的箭头显示了将每个质量`x[i]`移动`d[i]`的最小运输计划。 EMD 计算如下: - -![](img/B14853_05_014.png) (Equation 5.1.4) - -在“图 5.1.1”中,EMD 可解释为移动一堆污物`x`填充孔`y`所需的最少工作量。 尽管在此示例中,也可以从图中推导出`inf`,但在大多数情况下,尤其是在连续分布中,用尽所有可能的运输计划是很棘手的。 我们将在本章中稍后回到这个问题。 同时,我们将向您展示 GAN 损失函数的作用,实际上是如何使 **JS** 的差异最小化。 - -## GAN 中的距离函数 - -现在,在上一章的损失函数给定任何生成器的情况下,我们将计算最佳判别器。 我们将回顾上一章中的以下等式: - -![](img/B14853_05_015.png) (Equation 4.1.1) - -除了从噪声分布中采样外,前面的等式也可以表示为从生成器分布中采样: - -![](img/B14853_05_016.png) (Equation 5.1.5) - -找出最小的`L^(D)`: - -![](img/B14853_05_018.png) (Equation 5.1.6) - -![](img/B14853_05_019.png) (Equation 5.1.7) - -积分内部的项为`y -> a log(y) + b log(1 - y)`的形式,对于不包括`{0, 0}`的任何`a, b ∈ R^2`,在`y ∈ [0. 1]`的`a / (a + b)`处都有一个已知的最大值。 由于该积分不会更改此表达式的最大值(或`L^(D)`的最小值)的位置,因此最佳判别器为: - -![](img/B14853_05_026.png) (Equation 5.1.8) - -因此,给定最佳判别器的损失函数为: - -![](img/B14853_05_027.png) (Equation 5.1.9) - -![](img/B14853_05_028.png) (Equation 5.1.10) - -![](img/B14853_05_029.png) (Equation 5.1.11) - -![](img/B14853_05_030.png) (Equation 5.1.12) - -我们可以从“公式 5.1.12”观察到,最佳判别器的损失函数为常数减去真实分布`p_data`和任何生成器分布`p_g`之间的 JS 散度的两倍。 最小化`L^(D*)`意味着最大化`D[JS](p_data || p_g)`,否则判别器必须正确地将真实数据中的伪造物分类。 - -同时,我们可以放心地说,最佳生成器是当生成器分布等于真实数据分布时: - -![](img/B14853_05_034.png) (Equation 5.1.13) - -这是有道理的,因为生成器的目的是通过学习真实的数据分布来欺骗判别器。 有效地,我们可以通过最小化`D[JS]`或通过制作`p_g -> p_data`来获得最佳生成器。 给定最佳生成器,最佳判别器为`D*(x) = 1 / 2`和`L^(D*) = 2log2 = 0.60`。 - -问题在于,当两个分布没有重叠时,就没有平滑函数可以帮助缩小它们之间的差距。 训练 GAN 不会因梯度下降而收敛。 例如,假设: - -`p_data = (x, y) where x = 0, y ~ U(0, 1)` (Equation 5.1.14) - -`p_g = (x, y) where x = θ, y ~ U(0, 1)` (Equation 5.1.15) - -这两个分布显示在“图 5.1.2”中: - -![](img/B14853_05_02.png) - -图 5.1.2:没有重叠的两个分布的示例。 对于`p_g`,`θ = 0.5` - -![](img/B14853_05_044.png)是均匀分布。 每个距离函数的差异如下: - -* ![](img/B14853_05_045.png) -* ![](img/B14853_05_046.png) -* ![](img/B14853_05_047.png) -* ![](img/B14853_05_048.png) - -由于`D[JS]`是一个常数,因此 GAN 将没有足够的梯度来驱动`p_g -> p_data`。 我们还会发现`D[KL]`或反向`D[KL]`也不起作用。 但是,通过`W(p_data, p_g)`,我们可以拥有平滑函数,以便通过梯度下降获得`p_g -> p_data`。 为了优化 GAN,EMD 或 Wasserstein 1 似乎是一个更具逻辑性的损失函数,因为在两个分布具有极小或没有重叠的情况下,`D[JS]`会失败。 - -为了帮助进一步理解,可以在以下位置找到[有关距离函数的精彩讨论](https://lilianweng.github.io/lil-log/2017/08/20/from-GAN-to-WGAN.html)。 - -在下一节中,我们将重点介绍使用 EMD 或 Wasserstein 1 距离函数来开发替代损失函数,以鼓励稳定训练 GAN。 - -## 使用 Wasserstein 损失 - -在使用 EMD 或 Wasserstein 1 之前,还有一个要解决的问题。 耗尽`Π(p_data, p_g)`的空间来找到`γ ~ Π(p_data, p_g)`是很棘手的。 提出的解决方案是使用其 Kantorovich-Rubinstein 对偶: - -![](img/B14853_05_053.png) (Equation 5.1.16) - -等效地,EMD `sup ||f||_L <= 1`是所有 K-Lipschitz 函数上的最高值(大约是最大值):`f: x -> R`。 K-Lipschitz 函数满足以下约束: - -![](img/B14853_05_056.png) (Equation 5.1.17) - -对于所有`x[1], x[2] ∈ R`。 K-Lipschitz 函数具有有界导数,并且几乎总是连续可微的(例如,`f(x) = |x|`具有有界导数并且是连续的,但在`x = 0`时不可微分)。 - -“公式 5.1.16”可以通过找到 K-Lipschitz 函数`{f[w]}, w ∈ W`的族来求解: - -![](img/B14853_05_060.png) (Equation 5.1.18) - -在 GAN 中,可以通过从`z`-噪声分布采样并用`f[w]`替换“公式 5.1.18”来重写。 鉴别函数,`D[w]`: - -![](img/B14853_05_061.png) (Equation 5.1.19) - -我们使用粗体字母突出显示多维样本的一般性。 最后一个问题是如何找到函数族`w ∈ W`。 所提出的解决方案是在每次梯度更新时进行的。 判别器`w`的权重被限制在上下限之间(例如,-0.01 和 0.01): - -![](img/B14853_05_063.png) (Equation 5.1.20) - -`w`的较小值将判别器约束到紧凑的参数空间,从而确保 Lipschitz 连续性。 - -我们可以使用“公式 5.1.19”作为我们新的 GAN 损失函数的基础。 EMD 或 Wasserstein 1 是生成器旨在最小化的损失函数,以及判别器试图最大化的损失函数(或最小化`-W(p_data, p_g)`: - -![](img/B14853_05_064.png) (Equation 5.1.21) - -![](img/B14853_05_065.png) (Equation 5.1.22) - -在生成器损失函数中,第一项消失了,因为它没有针对实际数据进行直接优化。 - -“表 5.1.2”显示了 GAN 和 WGAN 的损失函数之间的差异。 为简洁起见,我们简化了`L^(D)`和`L^(G)`的表示法: - -| **网络** | **损失函数** | **公式** | -| --- | --- | --- | -| GAN | ![](img/B14853_05_068.png) | 4.1.1 | -| | ![](img/B14853_05_069.png) | 4.1.5 | -| WGAN | ![](img/B14853_05_070.png) | 5.1.21 | -| | ![](img/B14853_05_071.png) | 5.1.22 | -| | ![](img/B14853_05_072.png) | 5.1.20 | - -表 5.1.2:GAN 和 WGAN 的损失函数之间的比较 - -这些损失函数用于训练 WGAN,如“算法 5.1.1”中所示。 - -**算法 5.1.1 WGAN**。 参数的值为`α = 0.00005`,`c = 0.01`,`m = 64`和`n_critic = 5`。 - -要求:`α`,学习率。`c`是削波参数。`m`,批量大小。 `n_critic`,即每个生成器迭代的评论(鉴别)迭代次数。 - -要求:`w[D]`,初始判别器(discriminator)参数。 `θ[D]`,初始生成器参数: - -1. 当`θ[D]`尚未收敛,执行: -2. 对于`t = 1, ..., n_critic`,执行: -3. 从真实数据中抽样一批`{x^(i)} ~ p_data, i = 1, ..., m` -4. 从均匀的噪声分布中采样一批`{z^(i)} ~ p_x, i = 1, ..., m` -5. ![](img/B14853_05_085.png) - - 计算判别器梯度 -6. ![](img/B14853_05_086.png) - - 更新判别器参数 -7. ![](img/B14853_05_087.png) - - 剪辑判别器权重 -8. `end for` -9. 从均匀的噪声分布中采样一批`{z^(i)} ~ p_x, i = 1, ..., m` -10. ![](img/B14853_05_089.png) - - 计算生成器梯度 -11. ![](img/B14853_05_090.png) - - 更新生成器参数 -12. `end while` - -“图 5.1.3”展示了 WGAN 模型实际上与 DCGAN 相同,除了伪造的/真实的数据标签和损失函数: - -![](img/B14853_05_03.png) - -图 5.1.3:顶部:训练 WGAN 判别器需要来自生成器的虚假数据和来自真实分发的真实数据。 下:训练 WGAN 生成器要求生成器中假冒的真实数据是真实的 - -与 GAN 相似,WGAN 交替训练判别器和生成器(通过对抗)。 但是,在 WGAN 中,判别器(也称为评论者)在训练生成器进行一次迭代(第 9 至 11 行)之前,先训练`n_critic`迭代(第 2 至 8 行)。 这与对于判别器和生成器具有相同数量的训练迭代的 GAN 相反。 换句话说,在 GAN 中,`n_critic = 1`。 - -训练判别器意味着学习判别器的参数(权重和偏差)。 这需要从真实数据中采样一批(第 3 行),并从伪数据中采样一批(第 4 行),然后将采样数据馈送到判别器网络,然后计算判别器参数的梯度(第 5 行)。 判别器参数使用 RMSProp(第 6 行)进行了优化。 第 5 行和第 6 行都是“公式 5.1.21”的优化。 - -最后,EM 距离优化中的 Lipschitz 约束是通过裁剪判别器参数(第 7 行)来施加的。 第 7 行是“公式 5.1.20”的实现。 在`n_critic`迭代判别器训练之后,判别器参数被冻结。 生成器训练通过对一批伪造数据进行采样开始(第 9 行)。 采样的数据被标记为实数(1.0),以致愚弄判别器网络。 在第 10 行中计算生成器梯度,并在第 11 行中使用 RMSProp 对其进行优化。第 10 行和第 11 行执行梯度更新以优化“公式 5.1.22”。 - -训练生成器后,将解冻判别器参数,并开始另一个`n_critic`判别器训练迭代。 我们应该注意,在判别器训练期间不需要冻结生成器参数,因为生成器仅涉及数据的制造。 类似于 GAN,可以将判别器训练为一个单独的网络。 但是,训练生成器始终需要判别器通过对抗网络参与,因为损失是根据生成器网络的输出计算得出的。 - -与 GAN 不同,在 WGAN 中,将实际数据标记为 1.0,而将伪数据标记为 -1.0,作为计算第 5 行中的梯度的一种解决方法。第 5-6 和 10-11 行执行梯度更新以优化“公式 5.1.21”和“5.1.22”。 第 5 行和第 10 行中的每一项均建模为: - -![](img/B14853_05_091.png) (Equation 5.1.23) - -对于真实数据,其中`y_label = 1.0`,对于假数据,`y_label= -1.0`。 为了简化符号,我们删除了上标`(i)`。 对于判别器,当使用实际数据进行训练时,WGAN 增加`y_pred = D[w](x)`以最小化损失函数。 - -使用伪造数据进行训练时,WGAN 会降低`y_pred = D[w](g(z))`以最大程度地减少损失函数。 对于生成器,当在训练过程中将伪数据标记为真实数据时,WGAN 增加`y_pred = D[w](g(z))`以最小化损失函数。 请注意,`y_label`除了其符号外,对损失函数没有直接贡献。 在`tf.keras`中,“公式 5.1.23”实现为: - -```py -def wasserstein_loss(y_label, y_pred): - return -K.mean(y_label * y_pred) -``` - -本节最重要的部分是用于稳定训练 GAN 的新损失函数。 它基于 EMD 或 Wasserstein1。“算法 5.1.1”形式化了 WGAN 的完整训练算法,包括损失函数。 在下一节中,将介绍`tf.keras`中训练算法的实现。 - -## 使用 Keras 的 WGAN 实现 - -为了在`tf.keras`中实现 WGAN,我们可以重用 GAN 的 DCGAN 实现,这是我们在上一一章中介绍的。 DCGAN 构建器和工具函数在`lib`文件夹的`gan.py`中作为模块实现。 - -函数包括: - -* `generator()`:生成器模型构建器 -* `discriminator()`:判别器模型构建器 -* `train()`:DCGAN 训练师 -* `plot_images()`:通用生成器输出绘图仪 -* `test_generator()`:通用的生成器测试工具 - -如“列表 5.1.1”所示,我们可以通过简单地调用以下命令来构建一个判别器: - -```py -discriminator = gan.discriminator(inputs, activation='linear') -``` - -WGAN 使用线性输出激活。 对于生成器,我们执行: - -```py -generator = gan.generator(inputs, image_size) -``` - -`tf.keras`中的整体网络模型类似于 DCGAN 的“图 4.2.1”中看到的模型。 - -“列表 5.1.1”突出显示了 RMSprop 优化器和 Wasserstein 损失函数的使用。 在训练期间使用“算法 5.1.1”中的超参数。 - -[完整的代码可在 GitHub 上获得](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -“列表 5.1.1”:`wgan-mnist-5.1.2.py` - -```py -def build_and_train_models(): - """Load the dataset, build WGAN discriminator, - generator, and adversarial models. - Call the WGAN train routine. - """ - # load MNIST dataset - (x_train, _), (_, _) = mnist.load_data() -``` - -```py - # reshape data for CNN as (28, 28, 1) and normalize - image_size = x_train.shape[1] - x_train = np.reshape(x_train, [-1, image_size, image_size, 1]) - x_train = x_train.astype('float32') / 255 -``` - -```py - model_name = "wgan_mnist" - # network parameters - # the latent or z vector is 100-dim - latent_size = 100 - # hyper parameters from WGAN paper [2] - n_critic = 5 - clip_value = 0.01 - batch_size = 64 - lr = 5e-5 - train_steps = 40000 - input_shape = (image_size, image_size, 1) -``` - -```py - # build discriminator model - inputs = Input(shape=input_shape, name='discriminator_input') - # WGAN uses linear activation in paper [2] - discriminator = gan.discriminator(inputs, activation='linear') - optimizer = RMSprop(lr=lr) - # WGAN discriminator uses wassertein loss - discriminator.compile(loss=wasserstein_loss, - optimizer=optimizer, - metrics=['accuracy']) - discriminator.summary() -``` - -```py - # build generator model - input_shape = (latent_size, ) - inputs = Input(shape=input_shape, name='z_input') - generator = gan.generator(inputs, image_size) - generator.summary() -``` - -```py - # build adversarial model = generator + discriminator - # freeze the weights of discriminator during adversarial training - discriminator.trainable = False - adversarial = Model(inputs, - discriminator(generator(inputs)), - name=model_name) - adversarial.compile(loss=wasserstein_loss, - optimizer=optimizer, - metrics=['accuracy']) - adversarial.summary() -``` - -```py - # train discriminator and adversarial networks - models = (generator, discriminator, adversarial) - params = (batch_size, - latent_size, - n_critic, - clip_value, - train_steps, - model_name) - train(models, x_train, params) -``` - -“列表 5.1.2”是紧跟“算法 5.1.1”的训练函数。 但是,在判别器的训练中有一个小的调整。 与其在单个合并的真实数据和虚假数据中组合训练权重,不如先训练一批真实数据,然后再训练一批虚假数据。 这种调整将防止梯度消失,因为真实和伪造数据标签中的符号相反,并且由于裁剪而导致的权重较小。 - -“列表 5.1.2”:`wgan-mnist-5.1.2.py` - -为 WGAN 训练算法: - -```py -def train(models, x_train, params): - """Train the Discriminator and Adversarial Networks -``` - -```py - Alternately train Discriminator and Adversarial - networks by batch. - Discriminator is trained first with properly labelled - real and fake images for n_critic times. - Discriminator weights are clipped as a requirement - of Lipschitz constraint. - Generator is trained next (via Adversarial) with - fake images pretending to be real. - Generate sample images per save_interval -``` - -```py - Arguments: - models (list): Generator, Discriminator, - Adversarial models - x_train (tensor): Train images - params (list) : Networks parameters -``` - -```py - """ - # the GAN models - generator, discriminator, adversarial = models - # network parameters - (batch_size, latent_size, n_critic, - clip_value, train_steps, model_name) = params - # the generator image is saved every 500 steps - save_interval = 500 - # noise vector to see how the - # generator output evolves during training - noise_input = np.random.uniform(-1.0, - 1.0, - size=[16, latent_size]) - # number of elements in train dataset - train_size = x_train.shape[0] - # labels for real data - real_labels = np.ones((batch_size, 1)) - for i in range(train_steps): - # train discriminator n_critic times - loss = 0 - acc = 0 - for _ in range(n_critic): - # train the discriminator for 1 batch - # 1 batch of real (label=1.0) and - # fake images (label=-1.0) - # randomly pick real images from dataset - rand_indexes = np.random.randint(0, - train_size, - size=batch_size) - real_images = x_train[rand_indexes] - # generate fake images from noise using generator - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - fake_images = generator.predict(noise) -``` - -```py - # train the discriminator network - # real data label=1, fake data label=-1 - # instead of 1 combined batch of real and fake images, - # train with 1 batch of real data first, then 1 batch - # of fake images. - # this tweak prevents the gradient - # from vanishing due to opposite - # signs of real and fake data labels (i.e. +1 and -1) and - # small magnitude of weights due to clipping. - real_loss, real_acc = \ - discriminator.train_on_batch(real_images, - real_labels) - fake_loss, fake_acc = \ - discriminator.train_on_batch(fake_images, - -real_labels) - # accumulate average loss and accuracy - loss += 0.5 * (real_loss + fake_loss) - acc += 0.5 * (real_acc + fake_acc) - # clip discriminator weights to satisfy Lipschitz constraint - for layer in discriminator.layers: - weights = layer.get_weights() - weights = [np.clip(weight, - -clip_value, - clip_value) for weight in weights] - layer.set_weights(weights) -``` - -```py - # average loss and accuracy per n_critic training iterations - loss /= n_critic - acc /= n_critic - log = "%d: [discriminator loss: %f, acc: %f]" % (i, loss, acc) -``` - -```py - # train the adversarial network for 1 batch - # 1 batch of fake images with label=1.0 - # since the discriminator weights are frozen in - # adversarial network only the generator is trained - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # train the adversarial network - # note that unlike in discriminator training, - # we do not save the fake images in a variable - # the fake images go to the discriminator - # input of the adversarial for classification - # fake images are labelled as real - # log the loss and accuracy - loss, acc = adversarial.train_on_batch(noise, real_labels) - log = "%s [adversarial loss: %f, acc: %f]" % (log, loss, acc) - print(log) - if (i + 1) % save_interval == 0: - # plot generator images on a periodic basis - gan.plot_images(generator, - noise_input=noise_input, - show=False, - step=(i + 1), - model_name=model_name) -``` - -```py - # save the model after training the generator - # the trained generator can be reloaded - # for future MNIST digit generation - generator.save(model_name + ".h5") -``` - -“图 5.1.4”显示了 MNIST 数据集上 WGAN 输出的演变: - -![](img/B14853_05_04.png) - -图 5.1.4:WGAN 与训练步骤的示例输出。 在训练和测试期间,WGAN 的任何输出均不会遭受模式崩溃 - -即使在网络配置更改的情况下,WGAN 也稳定。 例如,当在识别符网络的 ReLU 之前插入批量规范化时,已知 DCGAN 不稳定。 在 WGAN 中,相同的配置是稳定的。 - -下图“图 5.1.5”向我们展示了 DCGAN 和 WGAN 的输出,并在判别器网络上进行了批量归一化: - -![](img/B14853_05_05.png) - -图 5.1.5:在判别器网络中的 ReLU 激活之前插入批量归一化时,DCGAN(左)和 WGAN(右)的输出比较 - -与上一章中的 GAN 训练相似,经过 40,000 个训练步骤,将训练后的模型保存在文件中。 使用训练有素的生成器模型,通过运行以下命令来生成新的合成 MNIST 数字图像: - -```py -python3 wgan-mnist-5.1.2.py --generator=wgan_mnist.h5 -``` - -正如我们所讨论的,原始 GAN 很难训练。 当 GAN 优化的损失函数时,就会出现问题。 实际上是在优化 *JS* 差异,`D[JS]`。 当两个分布函数之间几乎没有重叠时,很难优化`D[JS]`。 - -WGAN 提出通过使用 EMD 或 Wasserstein 1 损失函数来解决该问题,该函数即使在两个分布之间很少或没有重叠时也具有平滑的微分函数。 但是,WGAN 与生成的图像质量无关。 除了稳定性问题之外,原始 GAN 生成的图像在感知质量方面还有很多改进的地方。 LSGAN 理论上可以同时解决两个问题。 在下一节中,我们将介绍 LSGAN。 - -# 2\. 最小二乘 GAN(LSGAN) - -LSGAN 提出最小二乘损失。“图 5.2.1”演示了为什么在 GAN 中使用 Sigmoid 交叉熵损失会导致生成的数据质量较差: - -![](img/B14853_05_06.png) - -图 5.2.1:真实样本和虚假样本分布均除以各自的决策边界:Sigmoid 和最小二乘 - -理想情况下,假样本分布应尽可能接近真实样本的分布。 但是,对于 GAN,一旦伪样本已经位于决策边界的正确一侧,梯度就消失了。 - -这会阻止生成器具有足够的动机来提高生成的伪数据的质量。 远离决策边界的伪样本将不再试图靠近真实样本的分布。 使用最小二乘损失函数,只要假样本分布与真实样本的分布相距甚远,梯度就不会消失。 即使假样本已经位于决策边界的正确一侧,生成器也将努力改善其对实际密度分布的估计。 - -“表 5.2.1”显示了 GAN,WGAN 和 LSGAN 之间的损失函数的比较: - -| **网络** | **损失函数** | **公式** | -| --- | --- | --- | -| GAN | ![](img/B14853_05_095.png) | 4.1.1 | -| | ![](img/B14853_05_096.png) | 4.1.5 | -| WGAN | ![](img/B14853_05_097.png) | 5.1.21 | -| | ![](img/B14853_05_098.png) | 5.1.22 | -| | ![](img/B14853_05_099.png) | 5.1.20 | -| LSGAN | ![](img/B14853_05_100.png) | 5.2.1 | -| | ![](img/B14853_05_101.png) | 5.2.2 | - -表 5.2.1:GAN,WGAN 和 LSGAN 损失函数之间的比较 - -最小化“公式 5.2.1”或判别器损失函数意味着实际数据分类与真实标签 1.0 之间的 MSE 应该接近零。 此外,假数据分类和真实标签 0.0 之间的 MSE 应该接近零。 - -与其他 GAN 相似,对 LSGAN 判别器进行了训练,可以从假数据样本中对真实数据进行分类。 最小化公式 5.2.2 意味着在标签 1.0 的帮助下,使判别器认为生成的假样本数据是真实的。 - -以上一章中的 DCGAN 代码为基础来实现 LSGAN 仅需进行一些更改。 如“列表 5.2.1”所示,删除了判别器 Sigmoid 激活。 判别器是通过调用以下命令构建的: - -```py -discriminator = gan.discriminator(inputs, activation=None) -``` - -生成器类似于原始的 DCGAN: - -```py -generator = gan.generator(inputs, image_size) -``` - -鉴别函数和对抗损失函数都被`mse`代替。 所有网络参数均与 DCGAN 中的相同。 `tf.keras`中 LSGAN 的网络模型类似于“图 4.2.1”,除了存在线性激活或无输出激活外。 训练过程类似于 DCGAN 中的训练过程,由工具函数提供: - -```py -gan.train(models, x_train, params) -``` - -“列表 5.2.1”:`lsgan-mnist-5.2.1.py` - -```py -def build_and_train_models(): - """Load the dataset, build LSGAN discriminator, - generator, and adversarial models. - Call the LSGAN train routine. - """ - # load MNIST dataset - (x_train, _), (_, _) = mnist.load_data() - # reshape data for CNN as (28, 28, 1) and normalize - image_size = x_train.shape[1] - x_train = np.reshape(x_train, - [-1, image_size, image_size, 1]) - x_train = x_train.astype('float32') / 255 - model_name = "lsgan_mnist" - # network parameters - # the latent or z vector is 100-dim - latent_size = 100 - input_shape = (image_size, image_size, 1) - batch_size = 64 - lr = 2e-4 - decay = 6e-8 - train_steps = 40000 - # build discriminator model - inputs = Input(shape=input_shape, name='discriminator_input') - discriminator = gan.discriminator(inputs, activation=None) - # [1] uses Adam, but discriminator easily - # converges with RMSprop - optimizer = RMSprop(lr=lr, decay=decay) - # LSGAN uses MSE loss [2] - discriminator.compile(loss='mse', - optimizer=optimizer, - metrics=['accuracy']) - discriminator.summary() - # build generator model - input_shape = (latent_size, ) - inputs = Input(shape=input_shape, name='z_input') - generator = gan.generator(inputs, image_size) - generator.summary() - # build adversarial model = generator + discriminator - optimizer = RMSprop(lr=lr*0.5, decay=decay*0.5) - # freeze the weights of discriminator - # during adversarial training - discriminator.trainable = False - adversarial = Model(inputs, - discriminator(generator(inputs)), - name=model_name) - # LSGAN uses MSE loss [2] - adversarial.compile(loss='mse', - optimizer=optimizer, - metrics=['accuracy']) - adversarial.summary() - # train discriminator and adversarial networks - models = (generator, discriminator, adversarial) - params = (batch_size, latent_size, train_steps, model_name) - gan.train(models, x_train, params) -``` - -“图 5.2.2”显示了使用 MNIST 数据集对 40,000 个训练步骤进行 LSGAN 训练后生成的样本: - -![](img/B14853_05_07.png) - -图 5.2.2:LSGAN 的示例输出与训练步骤 - -与上一章中 DCGAN 中的“图 4.2.1”相比,输出图像的感知质量更好。 - -使用训练有素的生成器模型,通过运行以下命令来生成新的合成 MNIST 数字图像: - -```py -python3 lsgan-mnist-5.2.1.py --generator=lsgan_mnist.h5 -``` - -在本节中,我们讨论了损失函数的另一种改进。 通过使用 MSE 或 L2,我们解决了训练 GAN 的稳定性和感知质量的双重问题。 在下一节中,提出了相对于 CGAN 的另一项改进,这已在上一章中进行了讨论。 - -# 3\. 辅助分类器 GAN (ACGAN) - -ACGAN 在原理上类似于我们在上一章中讨论的**条件 GAN**(**CGAN**)。 我们将比较 CGAN 和 ACGAN。 对于 CGAN 和 ACGAN,生成器输入均为噪声及其标签。 输出是属于输入类标签的伪图像。 对于 CGAN,判别器的输入是图像(假的或真实的)及其标签。 输出是图像真实的概率。 对于 ACGAN,判别器的输入是一幅图像,而输出是该图像是真实的且其类别是标签的概率。 - -“图 5.3.1”突出显示了生成器训练期间 CGAN 和 ACGAN 之间的区别: - -![](img/B14853_05_08.png) - -图 5.3.1:CGAN 与 ACGAN 生成器训练。 主要区别是判别器的输入和输出 - -本质上,在 CGAN 中,我们向网络提供了边信息(标签)。 在 ACGAN 中,我们尝试使用辅助类解码器网络重建辅助信息。 ACGAN 理论认为,强制网络执行其他任务可以提高原始任务的表现。 在这种情况下,附加任务是图像分类。 原始任务是生成伪造图像。 - -“表 5.3.1”显示了 ACGAN 损失函数与 CGAN 损失函数的比较: - -| **网络** | **损失函数** | **编号** | -| --- | --- | --- | -| CGAN | ![](img/B14853_05_102.png) | 4.3.1 | -| | ![](img/B14853_05_103.png) | 4.3.2 | -| ACGAN | ![](img/B14853_05_104.png) | 5.3.1 | -| | ![](img/B14853_05_105.png) | 5.3.2 | - -表 5.3.1:CGAN 和 ACGAN 损失函数之间的比较 - -ACGAN 损失函数与 CGAN 相同,除了附加的分类器损失函数。 除了从假图片中识别真实图像的原始任务之外,判别器的“公式 5.3.1”还具有对真假图像正确分类的附加任务。 生成器的“公式 5.3.2”意味着,除了尝试用伪造的图像来欺骗判别器(`-E[z] log D(g(z | y))`)之外,它还要求判别器正确地对那些伪造的图像进行分类(`-E[z] log P(c | g(z | y))`)。 - -从 CGAN 代码开始,仅修改判别器和训练函数以实现 ACGAN。 `gan.py`还提供了判别器和生成器构建器函数。 要查看判别器上所做的更改,清单 5.3.1 显示了构建器函数,其中突出显示了执行图像分类的辅助解码器网络和双输出。 - -“列表 5.3.1”:`gan.py` - -```py -def discriminator(inputs, - activation='sigmoid', - num_labels=None, - num_codes=None): - """Build a Discriminator Model -``` - -```py - Stack of LeakyReLU-Conv2D to discriminate real from fake - The network does not converge with BN so it is not used here - unlike in [1] - Arguments: - inputs (Layer): Input layer of the discriminator (the image) - activation (string): Name of output activation layer - num_labels (int): Dimension of one-hot labels for ACGAN & InfoGAN - num_codes (int): num_codes-dim Q network as output - if StackedGAN or 2 Q networks if InfoGAN - - Returns: - Model: Discriminator Model - """ - kernel_size = 5 - layer_filters = [32, 64, 128, 256] -``` - -```py - x = inputs - for filters in layer_filters: - # first 3 convolution layers use strides = 2 - # last one uses strides = 1 - if filters == layer_filters[-1]: - strides = 1 - else: - strides = 2 - x = LeakyReLU(alpha=0.2)(x) - x = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - x = Flatten()(x) - # default output is probability that the image is real - outputs = Dense(1)(x) - if activation is not None: - print(activation) - outputs = Activation(activation)(outputs) -``` - -```py - if num_labels: - # ACGAN and InfoGAN have 2nd output - # 2nd output is 10-dim one-hot vector of label - layer = Dense(layer_filters[-2])(x) - labels = Dense(num_labels)(layer) - labels = Activation('softmax', name='label')(labels) - if num_codes is None: - outputs = [outputs, labels] - else: - # InfoGAN have 3rd and 4th outputs - # 3rd output is 1-dim continous Q of 1st c given x - code1 = Dense(1)(layer) - code1 = Activation('sigmoid', name='code1')(code1) -``` - -```py - # 4th output is 1-dim continuous Q of 2nd c given x - code2 = Dense(1)(layer) - code2 = Activation('sigmoid', name='code2')(code2) -``` - -```py - outputs = [outputs, labels, code1, code2] - elif num_codes is not None: - # StackedGAN Q0 output - # z0_recon is reconstruction of z0 normal distribution - z0_recon = Dense(num_codes)(x) - z0_recon = Activation('tanh', name='z0')(z0_recon) - outputs = [outputs, z0_recon] -``` - -```py - return Model(inputs, outputs, name='discriminator') -``` - -然后通过调用以下命令来构建判别器: - -```py -discriminator = gan.discriminator(inputs, num_labels=num_labels) -``` - -生成器与 WGAN 和 LSGAN 中的生成器相同。 回想一下,在以下“列表 5.3.2”中显示了生成器生成器。 我们应该注意,“列表 5.3.1”和“5.3.2”与上一节中 WGAN 和 LSGAN 使用的生成器函数相同。 重点介绍了适用于 LSGAN 的部件。 - -“列表 5.3.2”:`gan.py` - -```py -def generator(inputs, - image_size, - activation='sigmoid', - labels=None, - codes=None): - """Build a Generator Model -``` - -```py - Stack of BN-ReLU-Conv2DTranpose to generate fake images. - Output activation is sigmoid instead of tanh in [1]. - Sigmoid converges easily. -``` - -```py - Arguments: - inputs (Layer): Input layer of the generator (the z-vector) - image_size (int): Target size of one side - (assuming square image) - activation (string): Name of output activation layer - labels (tensor): Input labels - codes (list): 2-dim disentangled codes for InfoGAN -``` - -```py - Returns: - Model: Generator Model - """ - image_resize = image_size // 4 - # network parameters - kernel_size = 5 - layer_filters = [128, 64, 32, 1] -``` - -```py - if labels is not None: - if codes is None: - # ACGAN labels - # concatenate z noise vector and one-hot labels - inputs = [inputs, labels] - else: - # infoGAN codes - # concatenate z noise vector, - # one-hot labels and codes 1 & 2 - inputs = [inputs, labels] + codes - x = concatenate(inputs, axis=1) - elif codes is not None: - # generator 0 of StackedGAN - inputs = [inputs, codes] - x = concatenate(inputs, axis=1) - else: - # default input is just 100-dim noise (z-code) - x = inputs -``` - -```py - x = Dense(image_resize * image_resize * layer_filters[0])(x) - x = Reshape((image_resize, image_resize, layer_filters[0]))(x) -``` - -```py - for filters in layer_filters: - # first two convolution layers use strides = 2 - # the last two use strides = 1 - if filters > layer_filters[-2]: - strides = 2 - else: - strides = 1 - x = BatchNormalization()(x) - x = Activation('relu')(x) - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - if activation is not None: - x = Activation(activation)(x) -``` - -```py - # generator output is the synthesized image x - return Model(inputs, x, name='generator') -``` - -在 ACGAN 中,生成器实例化为: - -```py -generator = gan.generator(inputs, image_size, labels=labels) -``` - -“图 5.3.2”显示了`tf.keras`中 ACGAN 的网络模型: - -![](img/B14853_05_09.png) - -图 5.3.2:ACGAN 的`tf.keras`模型 - -如“列表 5.3.3”所示,对判别器和对抗模型进行了修改,以适应判别器网络中的更改。 现在,我们有两个损失函数。 首先是原始的二进制交叉熵,用于训练判别器来估计输入图像为实的概率。 - -第二个是图像分类器,用于预测类别标签。 输出是一个 10 维的单热向量。 - -“列表 5.3.3”:`acgan-mnist-5.3.1.py` - -重点介绍了在判别器和对抗网络中实现的更改: - -```py -def build_and_train_models(): - """Load the dataset, build ACGAN discriminator, - generator, and adversarial models. - Call the ACGAN train routine. - """ - # load MNIST dataset - (x_train, y_train), (_, _) = mnist.load_data() -``` - -```py - # reshape data for CNN as (28, 28, 1) and normalize - image_size = x_train.shape[1] - x_train = np.reshape(x_train, - [-1, image_size, image_size, 1]) - x_train = x_train.astype('float32') / 255 -``` - -```py - # train labels - num_labels = len(np.unique(y_train)) - y_train = to_categorical(y_train) -``` - -```py - model_name = "acgan_mnist" - # network parameters - latent_size = 100 - batch_size = 64 - train_steps = 40000 - lr = 2e-4 - decay = 6e-8 - input_shape = (image_size, image_size, 1) - label_shape = (num_labels, ) -``` - -```py - # build discriminator Model - inputs = Input(shape=input_shape, - name='discriminator_input') - # call discriminator builder - # with 2 outputs, pred source and labels - discriminator = gan.discriminator(inputs, - num_labels=num_labels) -``` - -```py - # [1] uses Adam, but discriminator - # easily converges with RMSprop - optimizer = RMSprop(lr=lr, decay=decay) - # 2 loss fuctions: 1) probability image is real - # 2) class label of the image - loss = ['binary_crossentropy', 'categorical_crossentropy'] - discriminator.compile(loss=loss, - optimizer=optimizer, - metrics=['accuracy']) - discriminator.summary() -``` - -```py - # build generator model - input_shape = (latent_size, ) - inputs = Input(shape=input_shape, name='z_input') - labels = Input(shape=label_shape, name='labels') - # call generator builder with input labels - generator = gan.generator(inputs, - image_size, - labels=labels) - generator.summary() -``` - -```py - # build adversarial model = generator + discriminator - optimizer = RMSprop(lr=lr*0.5, decay=decay*0.5) - # freeze the weights of discriminator - # during adversarial training - discriminator.trainable = False - adversarial = Model([inputs, labels], - discriminator(generator([inputs, labels])), - name=model_name) - # same 2 loss fuctions: 1) probability image is real - # 2) class label of the image - adversarial.compile(loss=loss, - optimizer=optimizer, - metrics=['accuracy']) - adversarial.summary() -``` - -```py - # train discriminator and adversarial networks - models = (generator, discriminator, adversarial) - data = (x_train, y_train) - params = (batch_size, latent_size, \ - train_steps, num_labels, model_name) - train(models, data, params) -``` - -在“列表 5.3.4”中,我们重点介绍了训练例程中实现的更改。 将与 CGAN 代码进行比较的主要区别在于,必须在鉴别和对抗训练中提供输出标签。 - -“列表 5.3.4”:`acgan-mnist-5.3.1.py` - -```py -def train(models, data, params): - """Train the discriminator and adversarial Networks - Alternately train discriminator and adversarial - networks by batch. - Discriminator is trained first with real and fake - images and corresponding one-hot labels. - Adversarial is trained next with fake images pretending - to be real and corresponding one-hot labels. - Generate sample images per save_interval. - # Arguments - models (list): Generator, Discriminator, - Adversarial models - data (list): x_train, y_train data - params (list): Network parameters - """ - # the GAN models - generator, discriminator, adversarial = models - # images and their one-hot labels - x_train, y_train = data - # network parameters - batch_size, latent_size, train_steps, num_labels, model_name \ - = params - # the generator image is saved every 500 steps - save_interval = 500 - # noise vector to see how the generator - # output evolves during training - noise_input = np.random.uniform(-1.0, - 1.0, - size=[16, latent_size]) - # class labels are 0, 1, 2, 3, 4, 5, - # 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 - # the generator must produce these MNIST digits - noise_label = np.eye(num_labels)[np.arange(0, 16) % num_labels] - # number of elements in train dataset - train_size = x_train.shape[0] - print(model_name, - "Labels for generated images: ", - np.argmax(noise_label, axis=1)) -``` - -```py - for i in range(train_steps): - # train the discriminator for 1 batch - # 1 batch of real (label=1.0) and fake images (label=0.0) - # randomly pick real images and - # corresponding labels from dataset - rand_indexes = np.random.randint(0, - train_size, - size=batch_size) - real_images = x_train[rand_indexes] - real_labels = y_train[rand_indexes] - # generate fake images from noise using generator - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # randomly pick one-hot labels - fake_labels = np.eye(num_labels)[np.random.choice(num_labels, - batch_size)] - # generate fake images - fake_images = generator.predict([noise, fake_labels]) - # real + fake images = 1 batch of train data - x = np.concatenate((real_images, fake_images)) - # real + fake labels = 1 batch of train data labels - labels = np.concatenate((real_labels, fake_labels)) -``` - -```py - # label real and fake images - # real images label is 1.0 - y = np.ones([2 * batch_size, 1]) - # fake images label is 0.0 - y[batch_size:, :] = 0 - # train discriminator network, log the loss and accuracy - # ['loss', 'activation_1_loss', - # 'label_loss', 'activation_1_acc', 'label_acc'] - metrics = discriminator.train_on_batch(x, [y, labels]) - fmt = "%d: [disc loss: %f, srcloss: %f," - fmt += "lblloss: %f, srcacc: %f, lblacc: %f]" - log = fmt % (i, metrics[0], metrics[1], \ - metrics[2], metrics[3], metrics[4]) -``` - -```py - # train the adversarial network for 1 batch - # 1 batch of fake images with label=1.0 and - # corresponding one-hot label or class - # since the discriminator weights are frozen - # in adversarial network only the generator is trained - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # randomly pick one-hot labels - fake_labels = np.eye(num_labels)[np.random.choice(num_labels, - batch_size)] - # label fake images as real - y = np.ones([batch_size, 1]) - # train the adversarial network - # note that unlike in discriminator training, - # we do not save the fake images in a variable - # the fake images go to the discriminator input - # of the adversarial for classification - # log the loss and accuracy - metrics = adversarial.train_on_batch([noise, fake_labels], - [y, fake_labels]) - fmt = "%s [advr loss: %f, srcloss: %f," - fmt += "lblloss: %f, srcacc: %f, lblacc: %f]" - log = fmt % (log, metrics[0], metrics[1],\ - metrics[2], metrics[3], metrics[4]) - print(log) - if (i + 1) % save_interval == 0: - # plot generator images on a periodic basis - gan.plot_images(generator, - noise_input=noise_input, - noise_label=noise_label, - show=False, - step=(i + 1), - model_name=model_name) -``` - -```py - # save the model after training the generator - # the trained generator can be reloaded - # for future MNIST digit generation - generator.save(model_name + ".h5") -``` - -可以看出,与其他任务相比,与我们之前讨论的所有 GAN 相比,ACGAN 的表现显着提高。 ACGAN 训练是稳定的,如“图 5.3.3”的 ACGAN 示例输出的以下标签所示: - -```py -[0 1 2 3 - 4 5 6 7 - 8 9 0 1 - 2 3 4 5] -``` - -与 CGAN 不同,样本输出的外观在训练过程中变化不大。 MNIST 数字图像的感知质量也更好。 - -![](img/B14853_05_10.png) - -图 5.3.3:ACGAN 根据标签的训练步骤生成的示例输出`[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5]` - -使用训练有素的生成器模型,通过运行以下命令来生成新的合成 MNIST 数字图像: - -```py -python3 acgan-mnist-5.3.1.py --generator=acgan_mnist.h5 -``` - -或者,也可以请求生成要生成的特定数字(例如 3): - -```py -python3 acgan-mnist-5.3.1.py --generator=acgan_mnist.h5 --digit=3 -``` - -“图 5.3.4”显示了 CGAN 和 ACGAN 产生的每个 MNIST 数字的并排比较。 ACGAN 中的数字 2-6 比 CGAN 中的数字质量更好: - -![](img/B14853_05_11.png) - -图 5.3.4:以数字 0 到 9 为条件的 CGAN 和 ACGAN 输出的并排比较 - -与 WGAN 和 LSGAN 相似,ACGAN 通过微调的损失函数,对现有 GAN CGAN 进行了改进。 在接下来的章节中,我们将发现新的损失函数,这些函数将使 GAN 能够执行新的有用任务。 - -# 4\. 总结 - -在本章中,我们介绍了对原始 GAN 算法的各种改进,这些改进在上一章中首次介绍。 WGAN 提出了一种通过使用 EMD 或 Wasserstein 1 损失来提高训练稳定性的算法。 LSGAN 认为,与最小二乘损失不同,GANs 的原始交叉熵函数倾向于消失梯度。 LSGAN 提出了一种实现稳定训练和高质量输出的算法。 ACGAN 通过要求判别器在确定输入图像是假的还是真实的基础上执行分类任务,来令人信服地提高了 MNIST 数字有条件生成的质量。 - -在下一章中,我们将研究如何控制生成器输出的属性。 尽管 CGAN 和 ACGAN 可以指示要生成的期望数字,但我们尚未分析可以指定输出属性的 GAN。 例如,我们可能想要控制 MNIST 数字的书写风格,例如圆度,倾斜角度和厚度。 因此,目标是引入具有纠缠表示的 GAN,以控制生成器输出的特定属性。 - -# 5\. 参考 - -1. `Ian Goodfellow et al.: Generative Adversarial Nets. Advances in neural information processing systems, 2014 (http://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf).` -1. `Martin Arjovsky, Soumith Chintala, and Léon Bottou: Wasserstein GAN. arXiv preprint, 2017 (https://arxiv.org/pdf/1701.07875.pdf).` -1. `Xudong Mao et al.: Least Squares Generative Adversarial Networks. 2017 IEEE International Conference on Computer Vision (ICCV). IEEE 2017 (http://openaccess.thecvf.com/content_ICCV_2017/papers/Mao_Least_Squares_Generative_ICCV_2017_paper.pdf).` -1. `Augustus Odena, Christopher Olah, and Jonathon Shlens. Conditional Image Synthesis with Auxiliary Classifier GANs. ICML, 2017 (http://proceedings.mlr.press/v70/odena17a/odena17a.pdf).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/06.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/06.md deleted file mode 100644 index 925fb4fa..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/06.md +++ /dev/null @@ -1,1374 +0,0 @@ -# 六、纠缠表示 GAN - -正如我们已经探索的那样,GAN 可以通过学习数据分布来产生有意义的输出。 但是,无法控制所生成输出的属性。 GAN 的一些变体,例如**条件 GAN**(**CGAN**)和**辅助分类器 GAN**(**ACGAN**),如前两章所讨论的,都可以训练生成器,该生成器可以合成特定的输出。 例如,CGAN 和 ACGAN 都可以诱导生成器生成特定的 MNIST 数字。 这可以通过同时使用 100 维噪声代码和相应的一号热标签作为输入来实现。 但是,除了单热标签外,我们没有其他方法可以控制生成的输出的属性。 - -有关 CGAN 和 ACGAN 的评论,请参阅“第 4 章”,“生成对抗网络(GANs)”和“第 5 章”,“改进的 GANs”。 - -在本章中,我们将介绍使我们能够修改生成器输出的 GAN 的变体。 在 MNIST 数据集的上下文中,除了要生成的数字外,我们可能会发现我们想要控制书写样式。 这可能涉及所需数字的倾斜度或宽度。 换句话说,GAN 也可以学习纠缠的潜在代码或表示形式,我们可以使用它们来改变生成器输出的属性。 解开的代码或表示形式是张量,可以在不影响其他属性的情况下更改输出数据的特定特征或属性。 - -在本章的第一部分中,我们将讨论《InfoGAN:通过最大化生成对抗网络的信息进行可解释的表示学习》[1],这是 GAN 的扩展。 InfoGAN 通过最大化输入代码和输出观察值之间的互信息来以无监督的方式学习解缠结的表示形式。 在 MNIST 数据集上,InfoGAN 从数字数据集中解开了写作风格。 - -在本章的以下部分中,我们还将讨论《栈式生成对抗网络或 StackedGAN》[2],这是 GAN 的另一种扩展。 - -StackedGAN 使用预训练的编码器或分类器,以帮助解开潜在代码。 StackedGAN 可以看作是一堆模型,每个模型都由编码器和 GAN 组成。 通过使用相应编码器的输入和输出数据,以对抗性方式训练每个 GAN。 - -总之,本章的目的是介绍: - -* 纠缠表示的概念 -* InfoGAN 和 StackedGAN 的原理 -* 使用`tf.keras`实现 InfoGAN 和 StackedGAN - -让我们从讨论纠缠的表示开始。 - -# 1\. 纠缠表示 - -最初的 GAN 能够产生有意义的输出,但是缺点是它的属性无法控制。 例如,如果我们训练 GAN 来学习名人面孔的分布,则生成器将产生名人形象的新图像。 但是,没有任何方法可以影响生成器有关所需脸部的特定属性。 例如,我们无法向生成器询问女性名人的脸,该女性名人是黑发,白皙的肤色,棕色的眼睛,微笑着。 这样做的根本原因是因为我们使用的 100 维噪声代码纠缠了生成器输出的所有显着属性。 我们可以回想一下,在`tf.keras`中,`100-dim`代码是由均匀噪声分布的随机采样生成的: - -```py - # generate fake images from noise using generator - # generate noise using uniform distribution - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - # generate fake images - fake_images = generator.predict(noise) -``` - -如果我们能够修改原始 GAN,以便将表示形式分为纠缠的和解缠的可解释的潜在代码向量,则我们将能够告诉生成器合成什么。 - -“图 6.1.1”向我们展示了一个带纠缠代码的 GAN,以及它的纠缠和解缠表示的混合形式。 在假设的名人脸生成的情况下,使用解开的代码,我们可以指出我们希望生成的脸的性别,发型,面部表情,肤色和肤色。 仍然需要`n–dim`纠缠代码来表示我们尚未纠缠的所有其他面部属性,例如面部形状,面部毛发,眼镜等,仅是三个示例。 纠缠和解纠缠的代码向量的连接用作生成器的新输入。 级联代码的总维不一定是 100: - -![](img/B14853_06_01.png) - -图 6.1.1:带有纠缠码的 GAN 及其随纠缠码和解缠码的变化。 此示例在名人脸生成的背景下显示 - -查看上图中的,似乎可以以与原始 GAN 相同的方式优化具有解缠表示的 GAN。 这是因为生成器的输出可以表示为: - -![](img/B14853_06_001.png) (Equation 6.1.1) - -代码`z = (z, c)`包含两个元素: - -* 类似于 GANs`z`或噪声向量的不可压缩纠缠噪声代码。 -* 潜在代码`c[1]`,`c[2]`,…,`c[L]`, 代表数据分配的可解译的纠缠码。 所有潜在代码共同表示为`c`。 - -为简单起见,假定所有潜在代码都是独立的: - -![](img/B14853_06_002.png) (Equation 6.1.2) - -生成器函数`x = g(z, c) = g(z)`带有不可压缩的噪声代码和潜在代码。 从生成器的角度来看,优化`z = (z, c)`与优化`z`相同。 - -当提出解决方案时,生成器网络将仅忽略解纠结代码所施加的约束。 - -生成器学习分布`p_g(x | c) = p_g(x)`。 这实际上将打乱分散表示的目的。 - -InfoGAN 的关键思想是强制 GAN 不要忽略潜在代码`c`。 这是通过最大化`c`和`g(z, c)`之间的相互信息来完成的。 在下一节中,我们将公式化 InfoGAN 的损失函数。 - -# InfoGAN - -为了加强对代码的纠缠,InfoGAN 提出了一种针对原始损失函数的正则化函数,该函数可最大化潜在代码`c`和`g(z, c)`之间的互信息: - -![](img/B14853_06_007.png) (Equation 6.1.3) - -正则化器在生成用于合成伪图像的函数时,会强制生成器考虑潜在代码。 在信息论领域,潜码`c`和`g(z, c)`之间的互信息定义为: - -![](img/B14853_06_009.png) (Equation 6.1.4) - -其中`H(c)`是潜码`c`的熵,`H(c | g(z | c))`是观察生成器的输出后`c`的条件熵, `g(z, c)`。 熵是对随机变量或事件的不确定性的度量。 例如,**在东方升起**之类的信息具有较低的熵,而**在彩票中赢得大奖**具有较高的熵。 可以在“第 13 章”,“使用互信息的无监督学习”中找到有关互信息的更详细讨论。 - -在“公式 6.1.4”中,最大化互信息意味着在观察生成的输出时,将`H(c | g(z | c))`最小化或减小潜码中的不确定性。 这是有道理的,因为例如在 MNIST 数据集中,如果 GAN 看到生成器 8 看到了数字 8,则生成器对合成数字 8 变得更有信心。 - -但是,`H(c | g(z | c))`很难估计,因为它需要后验`P(c | g(z | c)) = P(c | x)`的知识,这是我们无法获得的。 为简单起见,我们将使用常规字母`x`表示数据分布。 - -解决方法是通过使用辅助分布`Q(c | x)`估计后验来估计互信息的下界。 InfoGAN 估计相互信息的下限为: - -![](img/B14853_06_016.png) (Equation 6.1.5) - -在 InfoGAN 中,`H(c)`被假定为常数。 因此,使相互信息最大化是使期望最大化的问题。 生成器必须确信已生成具有特定属性的输出。 我们应注意,此期望的最大值为零。 因此,相互信息的下限的最大值为`H(c)`。 在 InfoGAN 中,离散隐码的`Q(c | x)`可以由`softmax`非线性表示。 期望是`tf.keras`中的负`categorical_crossentropy`损失。 - -对于一维连续代码,期望是`c`和`x`的双整数。 这是由于期望从纠缠的代码分布和生成器分布中采样。 估计期望值的一种方法是通过假设样本是连续数据的良好度量。 因此,损失估计为`c log Q(c | x)`。 在“第 13 章”,“使用互信息的无监督学习”中,我们将提供对互信息的更精确估计。 - -为了完成 InfoGAN 的网络,我们应该有`Q(c | x)`的实现。 为了简单起见,网络 Q 是一个附加到判别器第二到最后一层的辅助网络。 因此,这对原始 GAN 的训练影响很小。 - -“图 6.1.2”显示了 InfoGAN 网络图: - -![](img/B14853_06_02.png) - -图 6.1.2 网络图显示 InfoGAN 中的判别器和生成器训练 - -“表 6.1.1”显示了与 GAN 相比 InfoGAN 的损失函数: - -| **网络** | **损失函数** | **编号** | -| --- | --- | --- | -| GAN | ![](img/B14853_06_019.png) | 4.1.1 | -| | ![](img/B14853_06_020.png) | 4.1.5 | -| InfoGAN | ![](img/B14853_06_021.png) | 6.1.1 | -| | ![](img/B14853_06_022.png) | 6.1.2 | -| | 对于连续代码,InfoGAN 建议使用`λ < 1`的值。 在我们的示例中,我们设置`λ = 0.5`。 对于离散代码,InfoGAN 建议使用`λ = 1`。 | | - -表 6.1.1:GAN 和 InfoGAN 的损失函数之间的比较 - -InfoGAN 的损失函数与 GAN 的区别是附加项`-λI(c; g(z, c))`,其中`λ`是一个小的正常数。 最小化 InfoGAN 的损失函数可以将原始 GAN 的损失最小化,并将互信息最大化`I(c; g(z, c))`。 - -如果将其应用于 MNIST 数据集,InfoGAN 可以学习解开的离散码和连续码,以修改生成器输出属性。 例如,像 CGAN 和 ACGAN 一样,将使用`10-dim`一键标签形式的离散代码来指定要生成的数字。 但是,我们可以添加两个连续的代码,一个用于控制书写样式的角度,另一个用于调整笔划宽度。“图 6.1.3”显示了 InfoGAN 中 MNIST 数字的代码。 我们保留较小尺寸的纠缠代码以表示所有其他属性: - -![](img/B14853_06_03.png) - -图 6.1.3:MNIST 数据集中 GAN 和 InfoGAN 的代码 - -在讨论了 InfoGAN 背后的一些概念之后,让我们看一下`tf.keras`中的 InfoGAN 实现。 - -## 在 Keras 中实现 InfoGAN - -为了在 MNIST 数据集上实现 InfoGAN,需要对 ACGAN 的基本代码进行一些更改。 如“列表 6.1.1”中突出显示的那样,生成器将纠缠的(`z`噪声代码)和解纠结的代码(单标签和连续代码)连接起来作为输入: - -```py -inputs = [inputs, labels] + codes -``` - -`generator`和`discriminator`的构建器函数也在`lib`文件夹的`gan.py`中实现。 - -[完整的代码可在 GitHub 上获得](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -“列表 6.1.1”:`infogan-mnist-6.1.1.py` - -突出显示了特定于 InfoGAN 的行: - -```py -def generator(inputs, - image_size, - activation='sigmoid', - labels=None, - codes=None): - """Build a Generator Model -``` - -```py - Stack of BN-ReLU-Conv2DTranpose to generate fake images. - Output activation is sigmoid instead of tanh in [1]. - Sigmoid converges easily. -``` - -```py - Arguments: - inputs (Layer): Input layer of the generator (the z-vector) - image_size (int): Target size of one side - (assuming square image) - activation (string): Name of output activation layer - labels (tensor): Input labels - codes (list): 2-dim disentangled codes for InfoGAN -``` - -```py - Returns: - Model: Generator Model - """ - image_resize = image_size // 4 - # network parameters - kernel_size = 5 - layer_filters = [128, 64, 32, 1] -``` - -```py - if labels is not None: - if codes is None: - # ACGAN labels - # concatenate z noise vector and one-hot labels - inputs = [inputs, labels] - else: - # infoGAN codes - # concatenate z noise vector, - # one-hot labels and codes 1 & 2 - inputs = [inputs, labels] + codes - x = concatenate(inputs, axis=1) - elif codes is not None: - # generator 0 of StackedGAN - inputs = [inputs, codes] - x = concatenate(inputs, axis=1) - else: - # default input is just 100-dim noise (z-code) - x = inputs -``` - -```py - x = Dense(image_resize * image_resize * layer_filters[0])(x) - x = Reshape((image_resize, image_resize, layer_filters[0]))(x) -``` - -```py - for filters in layer_filters: - # first two convolution layers use strides = 2 - # the last two use strides = 1 - if filters > layer_filters[-2]: - strides = 2 - else: - strides = 1 - x = BatchNormalization()(x) - x = Activation('relu')(x) - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - if activation is not None: - x = Activation(activation)(x) -``` - -```py - # generator output is the synthesized image x - return Model(inputs, x, name='generator') -``` - -“列表 6.1.2”显示了具有原始默认 GAN 输出的判别器和 Q 网络。 高亮显示了三个辅助输出,它们对应于离散代码(用于单热标签)`softmax`预测的和给定输入 MNIST 数字图像的连续代码概率。 - -“列表 6.1.2”:`infogan-mnist-6.1.1.py` - -突出显示了特定于 InfoGAN 的行: - -```py -def discriminator(inputs, - activation='sigmoid', - num_labels=None, - num_codes=None): - """Build a Discriminator Model -``` - -```py - Stack of LeakyReLU-Conv2D to discriminate real from fake - The network does not converge with BN so it is not used here - unlike in [1] -``` - -```py - Arguments: - inputs (Layer): Input layer of the discriminator (the image) - activation (string): Name of output activation layer - num_labels (int): Dimension of one-hot labels for ACGAN & InfoGAN - num_codes (int): num_codes-dim Q network as output - if StackedGAN or 2 Q networks if InfoGAN -``` - -```py - Returns: - Model: Discriminator Model - """ - kernel_size = 5 - layer_filters = [32, 64, 128, 256] -``` - -```py - x = inputs - for filters in layer_filters: - # first 3 convolution layers use strides = 2 - # last one uses strides = 1 - if filters == layer_filters[-1]: - strides = 1 - else: - strides = 2 - x = LeakyReLU(alpha=0.2)(x) - x = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same')(x) -``` - -```py - x = Flatten()(x) - # default output is probability that the image is real - outputs = Dense(1)(x) - if activation is not None: - print(activation) - outputs = Activation(activation)(outputs) -``` - -```py - if num_labels: - # ACGAN and InfoGAN have 2nd output - # 2nd output is 10-dim one-hot vector of label - layer = Dense(layer_filters[-2])(x) - labels = Dense(num_labels)(layer) - labels = Activation('softmax', name='label')(labels) - if num_codes is None: - outputs = [outputs, labels] - else: - # InfoGAN have 3rd and 4th outputs - # 3rd output is 1-dim continous Q of 1st c given x - code1 = Dense(1)(layer) - code1 = Activation('sigmoid', name='code1')(code1) - # 4th output is 1-dim continuous Q of 2nd c given x - code2 = Dense(1)(layer) - code2 = Activation('sigmoid', name='code2')(code2) -``` - -```py - outputs = [outputs, labels, code1, code2] - elif num_codes is not None: - # StackedGAN Q0 output - # z0_recon is reconstruction of z0 normal distribution - z0_recon = Dense(num_codes)(x) - z0_recon = Activation('tanh', name='z0')(z0_recon) - outputs = [outputs, z0_recon] -``` - -```py - return Model(inputs, outputs, name='discriminator') -``` - -“图 6.1.4”显示了`tf.keras`中的 InfoGAN 模型: - -![](img/B14853_06_04.png) - -图 6.1.4:InfoGAN Keras 模型 - -建立判别器和对抗模型还需要进行许多更改。 更改取决于所使用的损失函数。 原始的判别器损失函数`binary_crossentropy`,用于离散码的`categorical_crossentropy`和每个连续码的`mi_loss`函数构成了整体损失函数。 除`mi_loss`函数的权重为 0.5(对应于连续代码的`λ = 0.5`)外,每个损失函数的权重均为 1.0。 - -“列表 6.1.3”突出显示了所做的更改。 但是,我们应该注意,通过使用构造器函数,判别器被实例化为: - -```py - # call discriminator builder with 4 outputs: - # source, label, and 2 codes - discriminator = gan.discriminator(inputs, - num_labels=num_labels, - num_codes=2) -``` - -生成器通过以下方式创建: - -```py - # call generator with inputs, - # labels and codes as total inputs to generator - generator = gan.generator(inputs, - image_size, - labels=labels, - codes=[code1, code2]) -``` - -“列表 6.1.3”:`infogan-mnist-6.1.1.py` - -以下代码演示了互信息损失函数以及建立和训练 InfoGAN 判别器和对抗网络的过程: - -```py -def mi_loss(c, q_of_c_given_x): - """ Mutual information, Equation 5 in [2], - assuming H(c) is constant - """ - # mi_loss = -c * log(Q(c|x)) - return K.mean(-K.sum(K.log(q_of_c_given_x + K.epsilon()) * c, - axis=1)) -``` - -```py -def build_and_train_models(latent_size=100): - """Load the dataset, build InfoGAN discriminator, - generator, and adversarial models. - Call the InfoGAN train routine. - """ -``` - -```py - # load MNIST dataset - (x_train, y_train), (_, _) = mnist.load_data() -``` - -```py - # reshape data for CNN as (28, 28, 1) and normalize - image_size = x_train.shape[1] - x_train = np.reshape(x_train, [-1, image_size, image_size, 1]) - x_train = x_train.astype('float32') / 255 -``` - -```py - # train labels - num_labels = len(np.unique(y_train)) - y_train = to_categorical(y_train) -``` - -```py - model_name = "infogan_mnist" - # network parameters - batch_size = 64 - train_steps = 40000 - lr = 2e-4 - decay = 6e-8 - input_shape = (image_size, image_size, 1) - label_shape = (num_labels, ) - code_shape = (1, ) -``` - -```py - # build discriminator model - inputs = Input(shape=input_shape, name='discriminator_input') - # call discriminator builder with 4 outputs: - # source, label, and 2 codes - discriminator = gan.discriminator(inputs, - num_labels=num_labels, - num_codes=2) - # [1] uses Adam, but discriminator converges easily with RMSprop - optimizer = RMSprop(lr=lr, decay=decay) - # loss functions: 1) probability image is real - # (binary crossentropy) - # 2) categorical cross entropy image label, - # 3) and 4) mutual information loss - loss = ['binary_crossentropy', - 'categorical_crossentropy', - mi_loss, - mi_loss] - # lamda or mi_loss weight is 0.5 - loss_weights = [1.0, 1.0, 0.5, 0.5] - discriminator.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - discriminator.summary() -``` - -```py - # build generator model - input_shape = (latent_size, ) - inputs = Input(shape=input_shape, name='z_input') - labels = Input(shape=label_shape, name='labels') - code1 = Input(shape=code_shape, name="code1") - code2 = Input(shape=code_shape, name="code2") - # call generator with inputs, - # labels and codes as total inputs to generator - generator = gan.generator(inputs, - image_size, - labels=labels, - codes=[code1, code2]) - generator.summary() -``` - -```py - # build adversarial model = generator + discriminator - optimizer = RMSprop(lr=lr*0.5, decay=decay*0.5) - discriminator.trainable = False - # total inputs = noise code, labels, and codes - inputs = [inputs, labels, code1, code2] - adversarial = Model(inputs, - discriminator(generator(inputs)), - name=model_name) - # same loss as discriminator - adversarial.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - adversarial.summary() -``` - -```py - # train discriminator and adversarial networks - models = (generator, discriminator, adversarial) - data = (x_train, y_train) - params = (batch_size, - latent_size, - train_steps, - num_labels, - model_name) - train(models, data, params) -``` - -就训练而言,我们可以看到 InfoGAN 与 ACGAN 类似,除了我们需要为连续代码提供`c`。`c`是从正态分布中提取的,标准差为 0.5,平均值为 0.0。 我们将对伪数据使用随机采样的标签,对实际数据使用数据集的类标签来表示离散的潜在代码。 - -“列表 6.1.4”突出显示了对训练函数所做的更改。 与以前的所有 GAN 相似,判别器和生成器(通过对抗性训练)被交替训练。 在对抗训练期间,判别器的权重被冻结。 - -通过使用`gan.py plot_images()`函数,样本生成器输出图像每 500 个间隔步被保存一次。 - -“列表 6.1.4”:`infogan-mnist-6.1.1.py` - -```py -def train(models, data, params): - """Train the Discriminator and Adversarial networks -``` - -```py - Alternately train discriminator and adversarial networks by batch. - Discriminator is trained first with real and fake images, - corresponding one-hot labels and continuous codes. - Adversarial is trained next with fake images pretending - to be real, corresponding one-hot labels and continous codes. - Generate sample images per save_interval. -``` - -```py - # Arguments - models (Models): Generator, Discriminator, Adversarial models - data (tuple): x_train, y_train data - params (tuple): Network parameters - """ - # the GAN models - generator, discriminator, adversarial = models - # images and their one-hot labels - x_train, y_train = data - # network parameters - batch_size, latent_size, train_steps, num_labels, model_name = \ - params - # the generator image is saved every 500 steps - save_interval = 500 - # noise vector to see how the generator output - # evolves during training - noise_input = np.random.uniform(-1.0, - 1.0, - size=[16, latent_size]) - # random class labels and codes - noise_label = np.eye(num_labels)[np.arange(0, 16) % num_labels] - noise_code1 = np.random.normal(scale=0.5, size=[16, 1]) - noise_code2 = np.random.normal(scale=0.5, size=[16, 1]) - # number of elements in train dataset - train_size = x_train.shape[0] - print(model_name, - "Labels for generated images: ", - np.argmax(noise_label, axis=1)) -``` - -```py - for i in range(train_steps): - # train the discriminator for 1 batch - # 1 batch of real (label=1.0) and fake images (label=0.0) - # randomly pick real images and - # corresponding labels from dataset - rand_indexes = np.random.randint(0, - train_size, - size=batch_size) - real_images = x_train[rand_indexes] - real_labels = y_train[rand_indexes] - # random codes for real images - real_code1 = np.random.normal(scale=0.5, - size=[batch_size, 1]) - real_code2 = np.random.normal(scale=0.5, - size=[batch_size, 1]) - # generate fake images, labels and codes - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - fake_labels = np.eye(num_labels)[np.random.choice(num_labels, - batch_size)] - fake_code1 = np.random.normal(scale=0.5, - size=[batch_size, 1]) - fake_code2 = np.random.normal(scale=0.5, - size=[batch_size, 1]) - inputs = [noise, fake_labels, fake_code1, fake_code2] - fake_images = generator.predict(inputs) - # real + fake images = 1 batch of train data - x = np.concatenate((real_images, fake_images)) - labels = np.concatenate((real_labels, fake_labels)) - codes1 = np.concatenate((real_code1, fake_code1)) - codes2 = np.concatenate((real_code2, fake_code2)) - # label real and fake images - # real images label is 1.0 - y = np.ones([2 * batch_size, 1]) - # fake images label is 0.0 - y[batch_size:, :] = 0 - # train discriminator network, - # log the loss and label accuracy - outputs = [y, labels, codes1, codes2] - # metrics = ['loss', 'activation_1_loss', 'label_loss', - # 'code1_loss', 'code2_loss', 'activation_1_acc', - # 'label_acc', 'code1_acc', 'code2_acc'] - # from discriminator.metrics_names - metrics = discriminator.train_on_batch(x, outputs) - fmt = "%d: [discriminator loss: %f, label_acc: %f]" - log = fmt % (i, metrics[0], metrics[6]) - # train the adversarial network for 1 batch - # 1 batch of fake images with label=1.0 and - # corresponding one-hot label or class + random codes - # since the discriminator weights are frozen - # in adversarial network only the generator is trained - # generate fake images, labels and codes - noise = np.random.uniform(-1.0, - 1.0, - size=[batch_size, latent_size]) - fake_labels = np.eye(num_labels)[np.random.choice(num_labels, - batch_size)] - fake_code1 = np.random.normal(scale=0.5, - size=[batch_size, 1]) - fake_code2 = np.random.normal(scale=0.5, - size=[batch_size, 1]) - # label fake images as real - y = np.ones([batch_size, 1]) - # train the adversarial network - # note that unlike in discriminator training, - # we do not save the fake images in a variable - # the fake images go to the discriminator - # input of the adversarial for classification - # log the loss and label accuracy - inputs = [noise, fake_labels, fake_code1, fake_code2] - outputs = [y, fake_labels, fake_code1, fake_code2] - metrics = adversarial.train_on_batch(inputs, outputs) - fmt = "%s [adversarial loss: %f, label_acc: %f]" - log = fmt % (log, metrics[0], metrics[6]) - print(log) - if (i + 1) % save_interval == 0: - # plot generator images on a periodic basis - gan.plot_images(generator, - noise_input=noise_input, - noise_label=noise_label, - noise_codes=[noise_code1, noise_code2], - show=False, - step=(i + 1), - model_name=model_name) - # save the model after training the generator - # the trained generator can be reloaded for - # future MNIST digit generation - generator.save(model_name + ".h5") -``` - -给定 InfoGAN 的`tf.keras`实现,下一个部分介绍具有解缠结属性的生成器 MNIST 输出。 - -## InfoGAN 的生成器输出 - -与以前提供给我们的所有 GAN 相似,我们已经对 InfoGAN 进行了 40,000 步的训练。 训练完成后,我们可以运行 InfoGAN 生成器,以使用`infogan_mnist.h5`文件中保存的模型生成新输出。 进行以下验证: - -1. 通过将离散标签从 0 更改为 9,可生成数字 0 至 9。 两个连续代码都设置为零。 结果显示在“图 6.1.5”中。 我们可以看到,InfoGAN 离散代码可以控制生成器产生的数字: - - ```py - python3 infogan-mnist-6.1.1.py --generator=infogan_mnist.h5 - --digit=0 --code1=0 --code2=0 - ``` - - 至 - - ```py - python3 infogan-mnist-6.1.1.py --generator=infogan_mnist.h5 - --digit=9 --code1=0 --code2=0 - ``` - - 在“图 6.1.5”中,我们可以看到 InfoGAN 生成的图像: - - ![](img/B14853_06_05.png) - - 图 6.1.5:当离散代码从 0 变为 9 时,InfoGAN 生成的图像都被设置为零。 - -2. 检查第一个连续代码的效果,以了解哪个属性已受到影响。 我们将 0 到 9 的第一个连续代码从 -2.0 更改为 2.0。 第二个连续代码设置为 0.0。 “图 6.1.6”显示了第一个连续代码控制数字的粗细: - - ```py - python3 infogan-mnist-6.1.1.py --generator=infogan_mnist.h5 - --digit=0 --code1=0 --code2=0 --p1 - ``` - - ![](img/B14853_06_06.png) - - 图 6.1.6:InfoGAN 作为第一个连续代码将 0 到 9 的数字从-2.0 更改为 2.0。第二个连续代码设置为零。 第一个连续代码控制数字的粗细 - -3. 与上一步的类似,但更多地关注第二个连续代码。“图 6.1.7”显示第二个连续代码控制书写样式的旋转角度(倾斜): - - ```py - python3 infogan-mnist-6.1.1.py --generator=infogan_mnist.h5 - --digit=0 --code1=0 --code2=0 --p2 - ``` - -![](img/B14853_06_07.png) - -图 6.1.7:InfoGAN 生成的图像作为第二个连续代码从 0 到 9 的数字从 -2.0 变为 2.0。第一个连续代码设置为零。 第二个连续代码控制书写样式的旋转角度(倾斜) - -从这些验证结果中,我们可以看到,除了生成 MNIST 外观数字的能力之外,InfoGAN 还扩展了条件 GAN(如 CGAN 和 ACGAN)的功能。 网络自动学习了两个可以控制生成器输出的特定属性的任意代码。 有趣的是,如果我们将连续代码的数量增加到 2 以上,可以控制哪些附加属性,可以通过将“列表 6.1.1”的突出显示行中的代码扩展到列表 6.1.4 来实现。 - -本节中的结果表明,可以通过最大化代码和数据分布之间的互信息来纠缠生成器输出的属性。 在以下部分中,介绍了一种不同的解缠结方法。 StackedGAN 的想法是在特征级别注入代码。 - -# 2\. StackedGAN - -与 InfoGAN 一样,StackedGAN 提出了一种用于分解潜在表示的方法,以调节生成器输出。 但是,StackedGAN 使用不同的方法来解决此问题。 与其学习如何调节噪声以产生所需的输出,不如将 StackedGAN 分解为 GAN 栈。 每个 GAN 均以通常的区分对手的方式进行独立训练,并带有自己的潜在代码。 - -“图 6.2.1”向我们展示了 StackedGAN 在假设名人脸生成的背景下如何工作,假设已经训练了*编码器*网络对名人脸进行分类: - -![](img/B14853_06_08.png) - -图 6.2.1:在名人脸生成的背景下 StackedGAN 的基本思想。 假设有一个假设的深层编码器网络可以对名人脸进行分类,那么 StackedGAN 可以简单地反转编码器的过程 - -*编码器*网络是由一堆简单的编码器组成的,`Encoder[i]`,其中`i = 0 … n-1`对应`n`个特征。 每个编码器都提取某些面部特征。 例如,`Encoder[0]`可能是发型特征的编码器,`Feature[1]`。 所有简单的编码器都有助于使整个*编码器*执行正确的预测。 - -StackedGAN 背后的想法是,如果我们想构建一个可生成假名人面孔的 GAN,则只需将*编码器*反转即可。 StackedGAN 由一堆更简单的 GAN 组成,`GAN[i]`,其中`i = 0 … n-1`与`n`个特征相对应。 每个`GAN[i]`学会反转其相应编码器`Encoder[i]`的过程。 例如,`GAN[0]`从假发型特征生成假名人脸,这是`Encoder[0]`处理的逆过程。 - -每个`GAN[i]`使用潜码`z[i]`,以调节其生成器输出。 例如,潜在代码`z[0]`可以将发型从卷曲更改为波浪形。 GAN 的栈也可以用作合成假名人面孔的对象,从而完成整个*编码器*的逆过程。 每个`GAN[i]`,`z[i]`的潜在代码都可以用来更改假名人面孔的特定属性。 - -有了 StackedGAN 的工作原理的关键思想,让我们继续下一节,看看如何在`tf.keras`中实现它。 - -## Keras 中 StackedGAN 的实现 - -StackedGAN 的详细网络模型可以在“图 6.2.2”中看到。 为简洁起见,每个栈仅显示两个编码器 GAN。 该图最初可能看起来很复杂,但这只是一个编码器 GAN 的重复,这意味着如果我们了解如何训练一个编码器 GAN,其余的将使用相同的概念。 - -在本节中,我们假设 StackedGAN 是为 MNIST 数字生成而设计的。 - -![](img/B14853_06_09.png) - -图 6.2.2:StackedGAN 包含编码器和 GAN 的栈。 对编码器进行预训练以执行分类。 `Generator[1]`,`G[1]`学会合成特征`f[1f]`,假标签`y[f]`和潜在代码`z[1f]`。 `Generator[0]`,`G[0]`均使用这两个伪特征`f[1f]`生成伪图像和潜在代码`z[0f]`。 - -StackedGAN 以*编码器*开头。 它可能是训练有素的分类器,可以预测正确的标签。 可以将中间特征向量`f[1r]`用于 GAN 训练。 对于 MNIST,我们可以使用基于 CNN 的分类器,类似于在“第 1 章”,“Keras 高级深度学习”中讨论的分类器。 - -“图 6.2.3”显示了*编码器*及其在`tf.keras`中的网络模型实现: - -![](img/B14853_06_10.png) - -图 6.2.3:StackedGAN 中的编码器是一个基于 CNN 的简单分类器 - -“列表 6.2.1”显示了上图的`tf.keras`代码。 它与“第 1 章”,“Keras 高级深度学习”中的基于 CNN 的分类器相似,不同之处在于,我们使用`Dense`层来提取`256-dim` 特征。 有两个输出模型,`Encoder[0]`和`Encoder[1]`。 两者都将用于训练 StackedGAN。 - -“列表 6.2.1”:`stackedgan-mnist-6.2.1.py` - -```py -def build_encoder(inputs, num_labels=10, feature1_dim=256): - """ Build the Classifier (Encoder) Model sub networks -``` - -```py - Two sub networks: - 1) Encoder0: Image to feature1 (intermediate latent feature) - 2) Encoder1: feature1 to labels -``` - -```py - # Arguments - inputs (Layers): x - images, feature1 - - feature1 layer output - num_labels (int): number of class labels - feature1_dim (int): feature1 dimensionality -``` - -```py - # Returns - enc0, enc1 (Models): Description below - """ - kernel_size = 3 - filters = 64 -``` - -```py - x, feature1 = inputs - # Encoder0 or enc0 - y = Conv2D(filters=filters, - kernel_size=kernel_size, - padding='same', - activation='relu')(x) - y = MaxPooling2D()(y) - y = Conv2D(filters=filters, - kernel_size=kernel_size, - padding='same', - activation='relu')(y) - y = MaxPooling2D()(y) - y = Flatten()(y) - feature1_output = Dense(feature1_dim, activation='relu')(y) -``` - -```py - # Encoder0 or enc0: image (x or feature0) to feature1 - enc0 = Model(inputs=x, outputs=feature1_output, name="encoder0") -``` - -```py - # Encoder1 or enc1 - y = Dense(num_labels)(feature1) - labels = Activation('softmax')(y) - # Encoder1 or enc1: feature1 to class labels (feature2) - enc1 = Model(inputs=feature1, outputs=labels, name="encoder1") -``` - -```py - # return both enc0 and enc1 - return enc0, enc1 -``` - -`Encoder[0]`输出`f[1r]`是我们想要的`256`维特征向量*生成器* 1 学习合成。 可以将用作`Encoder[0]`,`E[0]`的辅助输出。 训练整个*编码器*以对 MNIST 数字进行分类,即`x[r]`。 正确的标签`y[r]`由`Encoder[1]`,`E[1]`。 在此过程中,学习了的中间特征集`f[1r]`,可用于`Generator[0]`训练。 当针对该编码器训练 GAN 时,下标`r`用于强调和区分真实数据与伪数据。 - -假设*编码器*输入(`x[r]`)中间特征(`f[1r]`)和标签(`y[r]`),每个 GAN 都采用通常的区分-对抗方式进行训练。 损失函数由“表 6.2.1”中的“公式 6.2.1”至“公式 6.2.5”给出。“公式 6.2.1”和“公式 6.2.2”是通用 GAN 的常见损失函数。 StackedGAN 具有两个附加损失函数,即**有条件**和**熵**。 - -| **网络** | **损失函数** | **编号** | -| --- | --- | --- | -| GAN | ![](img/B14853_06_030.png) | 4.1.1 | -| | ![](img/B14853_06_031.png) | 4.1.5 | -| 栈式 | ![](img/B14853_06_032.png) | 6.2.1 | -| | ![](img/B14853_06_033.png) | 6.2.2 | -| | ![](img/B14853_06_034.png) | 6.2.3 | -| | ![](img/B14853_06_035.png) | 6.2.4 | -| | ![](img/B14853_06_036.png) | 6.2.5 | -| | 其中`λ1, λ2, λ3`是权重,`i`是编码器和 GAN ID | | - -表 6.2.1:GAN 和 StackedGAN 的损失函数之间的比较。 `~p_data`表示从相应的编码器数据(输入,特征或输出)采样 - -条件“公式 6.2.3”中的损失函数`L_i^(G_cond)`确保生成器不会忽略输入`f[i + 1]`, 当从输入噪声代码`z[i]`合成输出`f[i]`时。 编码器`Encoder[i]`必须能够通过反转生成器的过程`Generator[i]`来恢复生成器输入。 通过`L2`或欧几里德距离(**均方误差**(**MSE**))来测量生成器输入和使用编码器恢复的输入之间的差异。 - -“图 6.2.4”显示了`L_0^(G_cond)`计算所涉及的网络元素: - -![](img/B14853_06_11.png) - -图 6.2.4:图 6.2.3 的简化版本,仅显示`L_0^(G_cond)`计算中涉及的网络元素 - -但是,条件损失函数引入了一个新问题。 生成器忽略输入噪声代码`z[i]`,仅依赖`f[i + 1]`。 熵损失函数“公式 6.2.4”中的`L_0^(G_ent)`确保生成器不会忽略噪声代码`z[i]`。 *Q 网络*从生成器的输出中恢复噪声代码。 恢复的噪声和输入噪声之间的差异也通过`L2`或欧几里德距离(MSE)进行测量。 - -“图 6.2.5”显示了`L_0^(G_ent)`计算中涉及的网络元素: - -![](img/B14853_06_12.png) - -图 6.2.5:图 6.2.3 的简单版本仅向我们显示了`L_0^(G_ent)`计算中涉及的网络元素 - -最后的损失函数类似于通常的 GAN 损失。 它包括判别器损失`L_i^(D)`和生成器(通过对抗性)损失`L_i^(G_adv)`。“图 6.2.6”显示了 GAN 损失所涉及的元素。 - -![](img/B14853_06_13.png) - -图 6.2.6:图 6.2.3 的简化版本,仅显示了`L_i^(D)`和`L_0^(G_adv)`计算中涉及的网络元素 - -在“公式 6.2.5”中,三个生成器损失函数的加权和为最终生成器损失函数。 在我们将要介绍的 Keras 代码中,除的熵损失设置为 10.0 之外,所有权重都设置为 1.0。 在“公式 6.2.1”至“公式 6.2.5”中,`i`是指编码器和 GAN 组 ID 或级别。 在原始论文中,首先对网络进行独立训练,然后进行联合训练。 在独立训练期间,编码器将首先进行训练。 在联合训练期间,将使用真实数据和虚假数据。 - -`tf.keras`中 StackedGAN 生成器和判别器的实现只需进行少量更改即可提供辅助点来访问中间特征。“图 6.2.7”显示了生成器`tf.keras`模型。 - -![](img/B14853_06_14.png) - -图 6.2.7:Keras 中的 StackedGAN 生成器模型 - -“列表 6.2.2”说明了构建与`Generator[0]`和`Generator[1]`相对应的两个生成器(`gen0`和`gen1`)的函数。 `gen1`生成器由三层`Dense`层组成,标签为和噪声代码`z[1f]`作为输入。 第三层生成伪造的`f[1f]`特征。 `gen0`生成器类似于我们介绍的其他 GAN 生成器,可以使用`gan.py`中的生成器生成器实例化: - -```py -# gen0: feature1 + z0 to feature0 (image) -gen0 = gan.generator(feature1, image_size, codes=z0) -``` - -`gen0`输入为`f[1]`特征,并且噪声代码为`z[0]`。 输出是生成的伪图像`x[f]`: - -“列表 6.2.2”:`stackedgan-mnist-6.2.1.py` - -```py -def build_generator(latent_codes, image_size, feature1_dim=256): - """Build Generator Model sub networks -``` - -```py - Two sub networks: 1) Class and noise to feature1 - (intermediate feature) - 2) feature1 to image -``` - -```py - # Arguments - latent_codes (Layers): dicrete code (labels), - noise and feature1 features - image_size (int): Target size of one side - (assuming square image) - feature1_dim (int): feature1 dimensionality -``` - -```py - # Returns - gen0, gen1 (Models): Description below - """ -``` - -```py - # Latent codes and network parameters - labels, z0, z1, feature1 = latent_codes - # image_resize = image_size // 4 - # kernel_size = 5 - # layer_filters = [128, 64, 32, 1] -``` - -```py - # gen1 inputs - inputs = [labels, z1] # 10 + 50 = 62-dim - x = concatenate(inputs, axis=1) - x = Dense(512, activation='relu')(x) - x = BatchNormalization()(x) - x = Dense(512, activation='relu')(x) - x = BatchNormalization()(x) - fake_feature1 = Dense(feature1_dim, activation='relu')(x) - # gen1: classes and noise (feature2 + z1) to feature1 - gen1 = Model(inputs, fake_feature1, name='gen1') -``` - -```py - # gen0: feature1 + z0 to feature0 (image) - gen0 = gan.generator(feature1, image_size, codes=z0) -``` - -```py - return gen0, gen1 -``` - -“图 6.2.8”显示了判别器`tf.keras`模型: - -![](img/B14853_06_15.png) - -图 6.2.8:Keras 中的 StackedGAN 判别器模型 - -我们提供函数来构建`Discriminator[0]`和`Discriminator[1]`(`dis0`和`dis1`)。 `dis0`判别器类似于 GAN 判别器,除了特征向量输入和辅助网络`Q[0]`,其恢复`z[0]`。 `gan.py`中的构造器函数用于创建`dis0`: - -```py -dis0 = gan.discriminator(inputs, num_codes=z_dim) -``` - -`dis1`判别器由三层 MLP 组成,如清单 6.2.3 所示。 最后一层将区分为真假`f[1]`。`Q[1]`网络共享`dis1`的前两层。 其第三层回收`z[1]`。 - -“列表 6.2.3”:`stackedgan-mnist-6.2.1.py` - -```py -def build_discriminator(inputs, z_dim=50): - """Build Discriminator 1 Model -``` - -```py - Classifies feature1 (features) as real/fake image and recovers - the input noise or latent code (by minimizing entropy loss) -``` - -```py - # Arguments - inputs (Layer): feature1 - z_dim (int): noise dimensionality -``` - -```py - # Returns - dis1 (Model): feature1 as real/fake and recovered latent code - """ -``` - -```py - # input is 256-dim feature1 - x = Dense(256, activation='relu')(inputs) - x = Dense(256, activation='relu')(x) -``` - -```py - # first output is probability that feature1 is real - f1_source = Dense(1)(x) - f1_source = Activation('sigmoid', - name='feature1_source')(f1_source) -``` - -```py - # z1 reonstruction (Q1 network) - z1_recon = Dense(z_dim)(x) - z1_recon = Activation('tanh', name='z1')(z1_recon) -``` - -```py - discriminator_outputs = [f1_source, z1_recon] - dis1 = Model(inputs, discriminator_outputs, name='dis1') - return dis1 -``` - -有了所有可用的构建器函数,StackedGAN 就会在“列表 6.2.4”中进行组装。 在训练 StackedGAN 之前,对编码器进行了预训练。 请注意,我们已经在对抗模型训练中纳入了三个生成器损失函数(对抗,条件和熵)。`Q`网络与判别器模型共享一些公共层。 因此,其损失函数也被纳入判别器模型训练中。 - -“列表 6.2.4”:`stackedgan-mnist-6.2.1.py` - -```py -def build_and_train_models(): - """Load the dataset, build StackedGAN discriminator, - generator, and adversarial models. - Call the StackedGAN train routine. - """ - # load MNIST dataset - (x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py - # reshape and normalize images - image_size = x_train.shape[1] - x_train = np.reshape(x_train, [-1, image_size, image_size, 1]) - x_train = x_train.astype('float32') / 255 -``` - -```py - x_test = np.reshape(x_test, [-1, image_size, image_size, 1]) - x_test = x_test.astype('float32') / 255 -``` - -```py - # number of labels - num_labels = len(np.unique(y_train)) - # to one-hot vector - y_train = to_categorical(y_train) - y_test = to_categorical(y_test) -``` - -```py - model_name = "stackedgan_mnist" - # network parameters - batch_size = 64 - train_steps = 10000 - lr = 2e-4 - decay = 6e-8 - input_shape = (image_size, image_size, 1) - label_shape = (num_labels, ) - z_dim = 50 - z_shape = (z_dim, ) - feature1_dim = 256 - feature1_shape = (feature1_dim, ) -``` - -```py - # build discriminator 0 and Q network 0 models - inputs = Input(shape=input_shape, name='discriminator0_input') - dis0 = gan.discriminator(inputs, num_codes=z_dim) - # [1] uses Adam, but discriminator converges easily with RMSprop - optimizer = RMSprop(lr=lr, decay=decay) - # loss fuctions: 1) probability image is real (adversarial0 loss) - # 2) MSE z0 recon loss (Q0 network loss or entropy0 loss) - loss = ['binary_crossentropy', 'mse'] - loss_weights = [1.0, 10.0] - dis0.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - dis0.summary() # image discriminator, z0 estimator -``` - -```py - # build discriminator 1 and Q network 1 models - input_shape = (feature1_dim, ) - inputs = Input(shape=input_shape, name='discriminator1_input') - dis1 = build_discriminator(inputs, z_dim=z_dim ) - # loss fuctions: 1) probability feature1 is real - # (adversarial1 loss) - # 2) MSE z1 recon loss (Q1 network loss or entropy1 loss) - loss = ['binary_crossentropy', 'mse'] - loss_weights = [1.0, 1.0] - dis1.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - dis1.summary() # feature1 discriminator, z1 estimator -``` - -```py - # build generator models - feature1 = Input(shape=feature1_shape, name='feature1_input') - labels = Input(shape=label_shape, name='labels') - z1 = Input(shape=z_shape, name="z1_input") - z0 = Input(shape=z_shape, name="z0_input") - latent_codes = (labels, z0, z1, feature1) - gen0, gen1 = build_generator(latent_codes, image_size) - gen0.summary() # image generator - gen1.summary() # feature1 generator -``` - -```py - # build encoder models - input_shape = (image_size, image_size, 1) - inputs = Input(shape=input_shape, name='encoder_input') - enc0, enc1 = build_encoder((inputs, feature1), num_labels) - enc0.summary() # image to feature1 encoder - enc1.summary() # feature1 to labels encoder (classifier) - encoder = Model(inputs, enc1(enc0(inputs))) - encoder.summary() # image to labels encoder (classifier) -``` - -```py - data = (x_train, y_train), (x_test, y_test) - train_encoder(encoder, data, model_name=model_name) -``` - -```py - # build adversarial0 model = - # generator0 + discriminator0 + encoder0 - optimizer = RMSprop(lr=lr*0.5, decay=decay*0.5) - # encoder0 weights frozen - enc0.trainable = False - # discriminator0 weights frozen - dis0.trainable = False - gen0_inputs = [feature1, z0] - gen0_outputs = gen0(gen0_inputs) - adv0_outputs = dis0(gen0_outputs) + [enc0(gen0_outputs)] - # feature1 + z0 to prob feature1 is - # real + z0 recon + feature0/image recon - adv0 = Model(gen0_inputs, adv0_outputs, name="adv0") - # loss functions: 1) prob feature1 is real (adversarial0 loss) - # 2) Q network 0 loss (entropy0 loss) - # 3) conditional0 loss - loss = ['binary_crossentropy', 'mse', 'mse'] - loss_weights = [1.0, 10.0, 1.0] - adv0.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - adv0.summary() -``` - -```py - # build adversarial1 model = - # generator1 + discriminator1 + encoder1 - # encoder1 weights frozen - enc1.trainable = False - # discriminator1 weights frozen - dis1.trainable = False - gen1_inputs = [labels, z1] - gen1_outputs = gen1(gen1_inputs) - adv1_outputs = dis1(gen1_outputs) + [enc1(gen1_outputs)] - # labels + z1 to prob labels are real + z1 recon + feature1 recon - adv1 = Model(gen1_inputs, adv1_outputs, name="adv1") - # loss functions: 1) prob labels are real (adversarial1 loss) - # 2) Q network 1 loss (entropy1 loss) - # 3) conditional1 loss (classifier error) - loss_weights = [1.0, 1.0, 1.0] - loss = ['binary_crossentropy', - 'mse', - 'categorical_crossentropy'] - adv1.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - adv1.summary() -``` - -```py - # train discriminator and adversarial networks - models = (enc0, enc1, gen0, gen1, dis0, dis1, adv0, adv1) - params = (batch_size, train_steps, num_labels, z_dim, model_name) - train(models, data, params) -``` - -最后,训练函数与典型的 GAN 训练相似,不同之处在于我们一次只训练一个 GAN(即`GAN[0]`然后是`GAN[0]`)。 代码显示在“列表 6.2.5”中。 值得注意的是,训练顺序为: - -1. `Discriminator[1]`和`Q[1]`网络通过最小化判别器和熵损失 -2. `Discriminator[0]`和`Q[0]`网络通过最小化判别器和熵损失 -3. `Adversarial[1]`网络通过最小化对抗性,熵和条件损失 - -1. `Adversarial[0]`网络通过最小化对抗性,熵和条件损失 - -“列表 6.2.5”:`stackedgan-mnist-6.2.1.py` - -```py -def train(models, data, params): - """Train the discriminator and adversarial Networks -``` - -```py - Alternately train discriminator and adversarial networks by batch. - Discriminator is trained first with real and fake images, - corresponding one-hot labels and latent codes. - Adversarial is trained next with fake images pretending - to be real, corresponding one-hot labels and latent codes. - Generate sample images per save_interval. -``` - -```py - # Arguments - models (Models): Encoder, Generator, Discriminator, - Adversarial models - data (tuple): x_train, y_train data - params (tuple): Network parameters -``` - -```py - """ - # the StackedGAN and Encoder models - enc0, enc1, gen0, gen1, dis0, dis1, adv0, adv1 = models - # network parameters - batch_size, train_steps, num_labels, z_dim, model_name = params - # train dataset - (x_train, y_train), (_, _) = data - # the generator image is saved every 500 steps - save_interval = 500 -``` - -```py - # label and noise codes for generator testing - z0 = np.random.normal(scale=0.5, size=[16, z_dim]) - z1 = np.random.normal(scale=0.5, size=[16, z_dim]) - noise_class = np.eye(num_labels)[np.arange(0, 16) % num_labels] - noise_params = [noise_class, z0, z1] - # number of elements in train dataset - train_size = x_train.shape[0] - print(model_name, - "Labels for generated images: ", - np.argmax(noise_class, axis=1)) -``` - -```py - for i in range(train_steps): - # train the discriminator1 for 1 batch - # 1 batch of real (label=1.0) and fake feature1 (label=0.0) - # randomly pick real images from dataset - rand_indexes = np.random.randint(0, - train_size, - size=batch_size) - real_images = x_train[rand_indexes] - # real feature1 from encoder0 output - real_feature1 = enc0.predict(real_images) - # generate random 50-dim z1 latent code - real_z1 = np.random.normal(scale=0.5, - size=[batch_size, z_dim]) - # real labels from dataset - real_labels = y_train[rand_indexes] -``` - -```py - # generate fake feature1 using generator1 from - # real labels and 50-dim z1 latent code - fake_z1 = np.random.normal(scale=0.5, - size=[batch_size, z_dim]) - fake_feature1 = gen1.predict([real_labels, fake_z1]) -``` - -```py - # real + fake data - feature1 = np.concatenate((real_feature1, fake_feature1)) - z1 = np.concatenate((fake_z1, fake_z1)) -``` - -```py - # label 1st half as real and 2nd half as fake - y = np.ones([2 * batch_size, 1]) - y[batch_size:, :] = 0 -``` - -```py - # train discriminator1 to classify feature1 as - # real/fake and recover - # latent code (z1). real = from encoder1, - # fake = from genenerator1 - # joint training using discriminator part of - # advserial1 loss and entropy1 loss - metrics = dis1.train_on_batch(feature1, [y, z1]) - # log the overall loss only - log = "%d: [dis1_loss: %f]" % (i, metrics[0]) -``` - -```py - # train the discriminator0 for 1 batch - # 1 batch of real (label=1.0) and fake images (label=0.0) - # generate random 50-dim z0 latent code - fake_z0 = np.random.normal(scale=0.5, size=[batch_size, z_dim]) - # generate fake images from real feature1 and fake z0 - fake_images = gen0.predict([real_feature1, fake_z0]) - # real + fake data - x = np.concatenate((real_images, fake_images)) - z0 = np.concatenate((fake_z0, fake_z0)) - # train discriminator0 to classify image - # as real/fake and recover latent code (z0) - # joint training using discriminator part of advserial0 loss - # and entropy0 loss - metrics = dis0.train_on_batch(x, [y, z0]) - # log the overall loss only (use dis0.metrics_names) - log = "%s [dis0_loss: %f]" % (log, metrics[0]) -``` - -```py - # adversarial training - # generate fake z1, labels - fake_z1 = np.random.normal(scale=0.5, - size=[batch_size, z_dim]) - # input to generator1 is sampling fr real labels and - # 50-dim z1 latent code - gen1_inputs = [real_labels, fake_z1] -``` - -```py - # label fake feature1 as real - y = np.ones([batch_size, 1]) -``` - -```py - # train generator1 (thru adversarial) by fooling i - # the discriminator - # and approximating encoder1 feature1 generator - # joint training: adversarial1, entropy1, conditional1 - metrics = adv1.train_on_batch(gen1_inputs, - [y, fake_z1, real_labels]) - fmt = "%s [adv1_loss: %f, enc1_acc: %f]" - # log the overall loss and classification accuracy - log = fmt % (log, metrics[0], metrics[6]) -``` - -```py - # input to generator0 is real feature1 and - # 50-dim z0 latent code - fake_z0 = np.random.normal(scale=0.5, - size=[batch_size, z_dim]) - gen0_inputs = [real_feature1, fake_z0] -``` - -```py - # train generator0 (thru adversarial) by fooling - # the discriminator and approximating encoder1 imag - # source generator joint training: - # adversarial0, entropy0, conditional0 - metrics = adv0.train_on_batch(gen0_inputs, - [y, fake_z0, real_feature1]) - # log the overall loss only - log = "%s [adv0_loss: %f]" % (log, metrics[0]) -``` - -```py - print(log) - if (i + 1) % save_interval == 0: - generators = (gen0, gen1) - plot_images(generators, - noise_params=noise_params, - show=False, - step=(i + 1), - model_name=model_name) -``` - -```py - # save the modelis after training generator0 & 1 - # the trained generator can be reloaded for - # future MNIST digit generation - gen1.save(model_name + "-gen1.h5") - gen0.save(model_name + "-gen0.h5") -``` - -`tf.keras`中 StackedGAN 的代码实现现已完成。 训练后,可以评估生成器的输出以检查合成 MNIST 数字的某些属性是否可以以与我们在 InfoGAN 中所做的类似的方式进行控制。 - -## StackedGAN 的生成器输出 - -在对 StackedGAN 进行 10,000 步训练之后,`Generator[0]`和`Generator[1]`模型被保存在文件中。 `Generator[0]`和`Generator[1]`堆叠在一起可以合成以标签和噪声代码`z[0]`和`z[1]`为条件的伪造图像。 - -StackedGAN 生成器可以通过以下方式进行定性验证: - -1. 从两个噪声代码`z[0]`和`z[1]`的离散标签从 0 变到 9,从正态分布中采样,均值为 0.5,标准差为 1.0。 结果显示在“图 6.2.9”中。 我们可以看到 StackedGAN 离散代码可以控制生成器生成的数字: - - ```py - python3 stackedgan-mnist-6.2.1.py --generator0=stackedgan_mnist-gen0.h5 --generator1=stackedgan_mnist-gen1.h5 --digit=0 - ``` - - 至 - - ```py - python3 stackedgan-mnist-6.2.1.py --generator0=stackedgan_mnist-gen0.h5 --generator1=stackedgan_mnist-gen1.h5 --digit=9 - ``` - - ![](img/B14853_06_16.png) - - 图 6.2.9:当离散代码从 0 变为 9 时,StackedGAN 生成的图像。`z0`和`z1`均从正态分布中采样,平均值为 0,标准差为 0.5。 - -2. 如下所示,将第一噪声码`z[0]`从 -4.0 到 4.0 的恒定向量变为从 0 到 9 的数字。 第二噪声代码`z[1]`被设置为零向量。 “图 6.2.10”显示第一个噪声代码控制数字的粗细。 例如,对于数字 8: - - ```py - python3 stackedgan-mnist-6.2.1.py --generator0=stackedgan_mnist-gen0.h5 --generator1=stackedgan_mnist-gen1.h5 --z0=0 --z1=0 --p0 --digit=8 - ``` - - ![](img/B14853_06_17.png) - - 图 6.2.10:使用 StackedGAN 作为第一个噪声代码`z0`生成的图像,对于数字 0 到 9,其向量从 -4.0 到 4.0 不变。`z0`似乎控制着每个数字的粗细。 - -3. 如下所示,对于数字 0 到 9,从 -1.0 到 1.0 的恒定向量变化第二噪声代码`z[1]`。 将第一噪声代码`z[0]`设置为零向量。“图 6.2.11”显示第二个噪声代码控制旋转(倾斜),并在一定程度上控制手指的粗细。 例如,对于数字 8: - - ```py - python3 stackedgan-mnist-6.2.1.py --generator0=stackedgan_mnist-gen0.h5 --generator1=stackedgan_mnist-gen1.h5 --z0=0 --z1=0 --p1 --digit=8 - ``` - -![](img/B14853_06_18.png) - -图 6.2.11:由 StackedGAN 生成的图像作为第二个噪声代码`z1`从 0 到 9 的恒定向量 -1.0 到 1.0 变化。`z1`似乎控制着每个数字的旋转(倾斜)和笔划粗细 - -“图 6.2.9”至“图 6.2.11”证明 StackedGAN 提供了对生成器输出属性的附加控制。 控件和属性为(标签,哪个数字),(`z0`,数字粗细)和(`z1`,数字倾斜度)。 从此示例中,我们可以控制其他可能的实验,例如: - -* 从当前数量 2 增加栈中的元素数量 -* 像在 InfoGAN 中一样,减小代码`z[0]`和`z[1]`的尺寸 - -“图 6.2.12”显示了 InfoGAN 和 StackedGAN 的潜在代码之间的区别: - -![](img/B14853_06_19.png) - -图 6.2.12:不同 GAN 的潜在表示 - -解开代码的基本思想是对损失函数施加约束,以使仅特定属性受代码影响。 从结构上讲,与 StackedGAN 相比,InfoGAN 更易于实现。 InfoGAN 的训练速度也更快。 - -# 4\. 总结 - -在本章中,我们讨论了如何解开 GAN 的潜在表示。 在本章的前面,我们讨论了 InfoGAN 如何最大化互信息以迫使生成器学习解纠缠的潜向量。 在 MNIST 数据集示例中,InfoGAN 使用三种表示形式和一个噪声代码作为输入。 噪声以纠缠的形式表示其余的属性。 StackedGAN 以不同的方式处理该问题。 它使用一堆编码器 GAN 来学习如何合成伪造的特征和图像。 首先对编码器进行训练,以提供特征数据集。 然后,对编码器 GAN 进行联合训练,以学习如何使用噪声代码控制生成器输出的属性。 - -在下一章中,我们将着手一种新型的 GAN,它能够在另一个域中生成新数据。 例如,给定马的图像,GAN 可以将其自动转换为斑马的图像。 这种 GAN 的有趣特征是无需监督即可对其进行训练,并且不需要成对的样本数据。 - -# 5\. 参考 - -1. `Xi Chen et al.: InfoGAN: Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets. Advances in Neural Information Processing Systems, 2016 (http://papers.nips.cc/paper/6399-infogan-interpretable-representation-learning-by-information-maximizing-generative-adversarial-nets.pdf).` -1. `Xun Huang et al. Stacked Generative Adversarial Networks. IEEE Conference on Computer Vision and Pattern Recognition (CVPR). Vol. 2, 2017 (http://openaccess.thecvf.com/content_cvpr_2017/papers/Huang_Stacked_Generative_Adversarial_CVPR_2017_paper.pdf).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/07.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/07.md deleted file mode 100644 index a259129a..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/07.md +++ /dev/null @@ -1,990 +0,0 @@ -# 七、跨域 GAN - -在计算机视觉,计算机图形学和图像处理中,许多任务涉及将图像从一种形式转换为另一种形式。 灰度图像的着色,将卫星图像转换为地图,将一位艺术家的艺术品风格更改为另一位艺术家,将夜间图像转换为白天,将夏季照片转换为冬天只是几个例子。 这些任务被称为**跨域迁移**,将成为本章的重点。 源域中的图像将迁移到目标域,从而生成新的转换图像。 - -跨域迁移在现实世界中具有许多实际应用。 例如,在自动驾驶研究中,收集公路现场驾驶数据既费时又昂贵。 为了在该示例中覆盖尽可能多的场景变化,将在不同的天气条件,季节和时间中遍历道路,从而为我们提供了大量不同的数据。 使用跨域迁移,可以通过转换现有图像来生成看起来真实的新合成场景。 例如,我们可能只需要在夏天从一个区域收集道路场景,在冬天从另一地方收集道路场景。 然后,我们可以将夏季图像转换为冬季,并将冬季图像转换为夏季。 在这种情况下,它将必须完成的任务数量减少了一半。 - -现实的合成图像的生成是 GAN 擅长的领域。 因此,跨域翻译是 GAN 的应用之一。 在本章中,我们将重点介绍一种流行的跨域 GAN 算法,称为 *CycleGAN* [2]。 与其他跨域迁移算法(例如 *pix2pix* [3])不同,CycleGAN 不需要对齐的训练图像即可工作。 在对齐的图像中,训练数据应该是由源图像及其对应的目标图像组成的一对图像; 例如,卫星图像和从该图像得出的相应地图。 - -CycleGAN 仅需要卫星数据图像和地图。 这些地图可以来自其他卫星数据,而不必事先从训练数据中生成。 - -在本章中,我们将探讨以下内容: - -* CycleGAN 的原理,包括其在`tf.keras`中的实现 -* CycleGAN 的示例应用,包括使用 CIFAR10 数据集对灰度图像进行着色和应用于 MNIST 数字和*街景门牌号码(SVHN)* [1]数据集的样式迁移 - -让我们开始讨论 CycleGAN 背后的原理。 - -# 1\. CycleGAN 的原理 - -将图像从一个域转换到另一个域是计算机视觉,计算机图形学和图像处理中的常见任务。“图 7.1.1”显示了边缘检测,这是常见的图像转换任务: - -![](img/B14853_07_01.png) - -图 7.1.1:对齐图像对的示例:使用 Canny 边缘检测器的左,原始图像和右,变换后的图像。 原始照片是作者拍摄的。 - -在此示例中,我们可以将真实照片(左)视为源域中的图像,将边缘检测的照片(右)视为目标域中的样本。 还有许多其他具有实际应用的跨域翻译过程,例如: - -* 卫星图像到地图 -* 脸部图像到表情符号,漫画或动画 -* 身体图像到头像 -* 灰度照片的着色 -* 医学扫描到真实照片 -* 真实照片到画家的绘画 - -在不同领域中还有许多其他示例。 例如,在计算机视觉和图像处理中,我们可以通过发明一种从源图像中提取特征并将其转换为目标图像的算法来执行翻译。 坎尼边缘算子就是这种算法的一个例子。 但是,在很多情况下,翻译对于手工工程师而言非常复杂,因此几乎不可能找到合适的算法。 源域分布和目标域分布都是高维且复杂的。 - -解决图像翻译问题的一种方法是使用深度学习技术。 如果我们具有来自源域和目标域的足够大的数据集,则可以训练神经网络对转换进行建模。 由于必须在给定源图像的情况下自动生成目标域中的图像,因此它们必须看起来像是来自目标域的真实样本。 GAN 是适合此类跨域任务的网络。 *pix2pix* [3]算法是跨域算法的示例。 - -pix2pix 算法与**条件 GAN**(**CGAN**)[4]相似,我们在“第 4 章”,“生成对抗网络(GAN)”。 我们可以回想起在 CGAN 中,除了`z`噪声输入之外,诸如单热向量之类的条件会限制生成器的输出。 例如,在 MNIST 数字中,如果我们希望生成器输出数字 8,则条件为单热向量`[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]`。 在 pix2pix 中,条件是要翻译的图像。 生成器的输出是翻译后的图像。 通过优化 CGAN 损失来训练 pix2pix 算法。 为了使生成的图像中的模糊最小化,还包括 *L1* 损失。 - -类似于 pix2pix 的神经网络的主要缺点是训练输入和输出图像必须对齐。“图 7.1.1”是对齐的图像对的示例。 样本目标图像是从源生成的。 在大多数情况下,对齐的图像对不可用或无法从源图像生成,也不昂贵,或者我们不知道如何从给定的源图像生成目标图像。 我们拥有的是来自源域和目标域的样本数据。“图 7.1.2”是来自同一向日葵主题上源域(真实照片)和目标域(范高的艺术风格)的数据示例。 源图像和目标图像不一定对齐。 - -与 pix2pix 不同,CycleGAN 会学习图像翻译,只要源数据和目标数据之间有足够的数量和差异即可。 无需对齐。 CycleGAN 学习源和目标分布,以及如何从给定的样本数据中将源分布转换为目标分布。 无需监督。 在“图 7.1.2”的上下文中,我们只需要数千张真实向日葵的照片和数千张梵高向日葵画的照片。 在训练了 CycleGAN 之后,我们可以将向日葵的照片转换成梵高的画作: - -![A close up of a flower Description automatically generated](img/B14853_07_02.png) - -图 7.1.2:未对齐的图像对示例:左侧为菲律宾大学沿着大学大道的真实向日葵照片,右侧为伦敦国家美术馆的梵高的向日葵, 英国。 原始照片由作者拍摄。 - -下一个问题是:我们如何建立可以从未配对数据中学习的模型? 在下一部分中,我们将构建一个使用正向和反向循环 GAN 的 CycleGAN,以及一个循环一致性检查,以消除对配对输入数据的需求。 - -## CycleGAN 模型 - -“图 7.1.3”显示了 CycleGAN 的网络模型: - -![A close up of a logo Description automatically generated](img/B14853_07_03.png) - -图 7.1.3:CycleGAN 模型包含四个网络:生成器`G`,生成器`F`,判别器`D[y]`和判别器`D[x]` - -让我们逐个讨论“图 7.1.3”。 让我们首先关注上层网络,即转发周期 GAN。 如下图“图 7.1.4”所示,正向循环 CycleGAN 的目标是学习以下函数: - -![](img/B14853_07_001.png) (Equation 7.1.1) - -![A close up of a logo Description automatically generated](img/B14853_07_04.png) - -图 7.1.4:伪造`y`的 CycleGAN 生成器`G` - -“公式 7.1.1”只是假目标数据`y'`的生成器`G`。 它将数据从源域`x`转换为目标域`y`。 - -要训​​练生成器,我们必须构建 GAN。 这是正向循环 GAN,如图“图 7.1.5”所示。 该图表明,它类似于“第 4 章”,“生成对抗网络(GANs)”中的典型 GAN,由生成器`G`和判别器`D[y]`组成,它可以以相同的对抗方式进行训练。通过仅利用源域中的可用实际图像`x`和目标域中的实际图像`y`,进行无监督学习。 - -![A close up of a logo Description automatically generated](img/B14853_07_05.png) - -图 7.1.5:CycleGAN 正向循环 GAN - -与常规 GAN 不同,CycleGAN 施加了周期一致性约束,如图“图 7.1.6”所示。 前向循环一致性网络可确保可以从伪造的目标数据中重建真实的源数据: - -![](img/B14853_07_004.png) (Equation 7.1.2) - -![](img/B14853_07_06.png) - -图 7.1.6:CycleGAN 循环一致性检查 - -通过最小化正向循环一致性 *L1* 损失来完成: - -![](img/B14853_07_005.png) (Equation 7.1.3) - -周期一致性损失使用 *L1* 或**平均绝对误差**(**MAE**),因为与 *L2* 或**均方误差**(**MSE**)相比,它通常导致较少的模糊图像重建。 - -循环一致性检查表明,尽管我们已将源数据`x`转换为域`y`,但`x`的原始特征仍应保留在`y`中并且可恢复。 网络`F`只是我们将从反向循环 GAN 借用的另一个生成器,如下所述。 - -CycleGAN 是对称的。 如图“图 7.1.7”所示,后向循环 GAN 与前向循环 GAN 相同,但将源数据`x`和目标数据`y`的作用逆转。 现在,源数据为`y`,目标数据为`x`。 生成器`G`和`F`的作用也相反。`F`现在是生成器,而`G`恢复输入。 在正向循环 GAN 中,生成器`F`是用于恢复源数据的网络,而`G`是生成器。 - -Backward Cycle GAN 生成器的目标是合成: - -![](img/B14853_07_006.png) (Equation 7.1.2) - -![A close up of a logo Description automatically generated](img/B14853_07_07.png) - -图 7.1.7:CycleGAN 向后循环 GAN - -这可以通过对抗性训练反向循环 GAN 来完成。 目的是让生成器`F`学习如何欺骗判别器`D[x]`。 - -此外,还具有类似的向后循环一致性,以恢复原始源`y`: - -![](img/B14853_07_008.png) (Equation 7.1.4) - -这是通过最小化后向循环一致性 *L1* 损失来完成的: - -![](img/B14853_07_009.png) (Equation 7.1.5) - -总而言之,CycleGAN 的最终目标是使生成器`G`学习如何合成伪造的目标数据`y'`,该伪造的目标数据`y'`会在正向循环中欺骗识别器`D[y]`。 由于网络是对称的,因此 CycleGAN 还希望生成器`F`学习如何合成伪造的源数据`x'`,该伪造的源数据可以使判别器`D[x]`在反向循环中蒙蔽。 考虑到这一点,我们现在可以将所有损失函数放在一起。 - -让我们从 GAN 部分开始。 受到*最小二乘 GAN(LSGAN)* [5]更好的感知质量的启发,如“第 5 章”,“改进的 GAN” 中所述,CycleGAN 还使用 MSE 作为判别器和生成器损失。 回想一下,LSGAN 与原始 GAN 之间的差异需要使用 MSE 损失,而不是二进制交叉熵损失。 - -CycleGAN 将生成器-标识符损失函数表示为: - -![](img/B14853_07_014.png) (Equation 7.1.6) - -![](img/B14853_07_015.png) (Equation 7.1.7) - -![](img/B14853_07_016.png) (Equation 7.1.8) - -![](img/B14853_07_017.png) (Equation 7.1.9) - -![](img/B14853_07_018.png) (Equation 7.1.10) - -![](img/B14853_07_019.png) (Equation 7.1.11) - -损失函数的第二组是周期一致性损失,可以通过汇总前向和后向 GAN 的贡献来得出: - -![](img/B14853_07_020.png) - -![](img/B14853_07_021.png) (Equation 7.1.12) - -CycleGAN 的总损失为: - -![](img/B14853_07_022.png) (Equation 7.1.13) - -CycleGAN 建议使用以下权重值`λ1 = 1.0`和`λ2 = 10.0`,以更加重视循环一致性检查。 - -训练策略类似于原始 GAN。 “算法 7.1.1”总结了 CycleGAN 训练过程。 - -“算法 7.1.1”:CycleGAN 训练 - -对`n`训练步骤重复上述步骤: - -1. 通过使用真实的源数据和目标数据训练前向循环判别器,将`L_forward_GAN^(D)`降至最低。 实际目标数据的小批量`y`标记为 1.0。 伪造的目标数据`y' = G(x)`的小批量标记为 0.0。 -2. 通过使用真实的源数据和目标数据训练反向循环判别器,将`L_backward_GAN^(D)`最小化。 实际源数据的小批量`x`标记为 1.0。 一小部分伪造的源数据`x' = F(y)`被标记为 0.0。 - -1. 通过训练对抗网络中的前向周期和后向周期生成器,将`L_GAN^(D)`和`L_cyc`最小化。 伪造目标数据的一个小批量`y' = G(x)`被标记为 1.0。 一小部分伪造的源数据`x' = F(y)`被标记为 1.0。 判别器的权重被冻结。 - -在神经样式迁移问题中,颜色组合可能无法成功地从源图像迁移到伪造目标图像。 此问题显示在“图 7.1.8”中: - -![](img/B14853_07_08.png) - -图 7.1.8:在样式迁移过程中,颜色组合可能无法成功迁移。 为了解决此问题,将恒等损失添加到总损失函数中 - -为了解决这个问题,CycleGAN 建议包括正向和反向循环身份损失函数: - -![](img/B14853_07_033.png) (Equation 7.1.14) - -CycleGAN 的总损失变为: - -![](img/B14853_07_034.png) (Equation 7.1.15) - -其中`λ3 = 0.5`。 在对抗训练中,身份损失也得到了优化。“图 7.1.9”重点介绍了实现身份正则器的 CycleGAN 辅助网络: - -![A screenshot of a cell phone Description automatically generated](img/B14853_07_09.png) - -图 7.1.9:具有身份正则化网络的 CycleGAN 模型,图像左侧突出显示 - -在下一个部分,我们将在`tf.keras`中实现 CycleGAN。 - -## 使用 Keras 实现 CycleGAN - -我们来解决,这是 CycleGAN 可以解决的简单问题。 在“第 3 章”,“自编码器”中,我们使用了自编码器为 CIFAR10 数据集中的灰度图像着色。 我们可以记得,CIFAR10 数据集包含 50,000 个训练过的数据项和 10,000 个测试数据样本,这些样本属于 10 个类别的`32 x 32` RGB 图像。 我们可以使用`rgb2gray`(RGB)将所有彩色图像转换为灰度图像,如“第 3 章”,“自编码器”中所述。 - -接下来,我们可以将灰度训练图像用作源域图像,将原始彩色图像用作目标域图像。 值得注意的是,尽管数据集是对齐的,但我们 CycleGAN 的输入是彩色图像的随机样本和灰度图像的随机样本。 因此,我们的 CycleGAN 将看不到训练数据对齐。 训练后,我们将使用测试的灰度图像来观察 CycleGAN 的表现。 - -如前几节所述,要实现 CycleGAN,我们需要构建两个生成器和两个判别器。 CycleGAN 的生成器学习源输入分布的潜在表示,并将该表示转换为目标输出分布。 这正是自编码器的功能。 但是,类似于“第 3 章”,“自编码器”中讨论的典型自编码器,使用的编码器会对输入进行下采样,直到瓶颈层为止,此时解码器中的处理过程相反。 - -由于在编码器和解码器层之间共享许多低级特征,因此该结构不适用于某些图像转换问题。 例如,在着色问题中,灰度图像的形式,结构和边缘与彩色图像中的相同。 为了解决这个问题,CycleGAN 生成器使用 *U-Net* [7]结构,如图“图 7.1.10”所示: - -![](img/B14853_07_10.png) - -图 7.1.10:在 Keras 中实现正向循环生成器`G`。 产生器是包括编码器和解码器的 U 网络[7]。 - -在 U-Net 结构中,编码器层的输出`e[ni]`与解码器层的输出`d[i]`,其中`n = 4`是编码器/解码器的层数,`i = 1, 2, 3`是共享信息的层号。 - -我们应该注意,尽管该示例使用`n = 4`,但输入/输出尺寸较大的问题可能需要更深的编码器/解码器层。 通过 U-Net 结构,可以在编码器和解码器之间自由迁移特征级别的信息。 - -编码器层由`Instance Normalization(IN)-LeakyReLU-Conv2D`组成,而解码器层由`IN-ReLU-Conv2D`组成。 编码器/解码器层的实现如清单 7.1.1 所示,而生成器的实现如列表 7.1.2 所示。 - -[完整的代码可在 GitHub 上找到](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -**实例规范化**(**IN**)是每个数据(即 IN 是图像或每个特征的 BN)。 在样式迁移中,重要的是标准化每个样本而不是每个批量的对比度。 IN 等于,相当于对比度归一化。 同时,BN 打破了对比度标准化。 - -记住在使用 IN 之前先安装`tensorflow-addons`: - -```py -$ pip install tensorflow-addons -``` - -“列表 7.1.1”:`cyclegan-7.1.1.py` - -```py -def encoder_layer(inputs, - filters=16, - kernel_size=3, - strides=2, - activation='relu', - instance_norm=True): - """Builds a generic encoder layer made of Conv2D-IN-LeakyReLU - IN is optional, LeakyReLU may be replaced by ReLU - """ -``` - -```py - conv = Conv2D(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same') -``` - -```py - x = inputs - if instance_norm: - x = InstanceNormalization(axis=3)(x) - if activation == 'relu': - x = Activation('relu')(x) - else: - x = LeakyReLU(alpha=0.2)(x) - x = conv(x) - return x -``` - -```py -def decoder_layer(inputs, - paired_inputs, - filters=16, - kernel_size=3, - strides=2, - activation='relu', - instance_norm=True): - """Builds a generic decoder layer made of Conv2D-IN-LeakyReLU - IN is optional, LeakyReLU may be replaced by ReLU - Arguments: (partial) - inputs (tensor): the decoder layer input - paired_inputs (tensor): the encoder layer output - provided by U-Net skip connection & - concatenated to inputs. - """ -``` - -```py - conv = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - strides=strides, - padding='same') -``` - -```py - x = inputs - if instance_norm: - x = InstanceNormalization(axis=3)(x) - if activation == 'relu': - x = Activation('relu')(x) - else: - x = LeakyReLU(alpha=0.2)(x) - x = conv(x) - x = concatenate([x, paired_inputs]) - return x -``` - -将移至生成器实现中: - -“列表 7.1.2”:`cyclegan-7.1.1.py` - -Keras 中的生成器实现: - -```py -def build_generator(input_shape, - output_shape=None, - kernel_size=3, - name=None): - """The generator is a U-Network made of a 4-layer encoder - and a 4-layer decoder. Layer n-i is connected to layer i. -``` - -```py - Arguments: - input_shape (tuple): input shape - output_shape (tuple): output shape - kernel_size (int): kernel size of encoder & decoder layers - name (string): name assigned to generator model -``` - -```py - Returns: - generator (Model): - """ -``` - -```py - inputs = Input(shape=input_shape) - channels = int(output_shape[-1]) - e1 = encoder_layer(inputs, - 32, - kernel_size=kernel_size, - activation='leaky_relu', - strides=1) - e2 = encoder_layer(e1, - 64, - activation='leaky_relu', - kernel_size=kernel_size) - e3 = encoder_layer(e2, - 128, - activation='leaky_relu', - kernel_size=kernel_size) - e4 = encoder_layer(e3, - 256, - activation='leaky_relu', - kernel_size=kernel_size) -``` - -```py - d1 = decoder_layer(e4, - e3, - 128, - kernel_size=kernel_size) - d2 = decoder_layer(d1, - e2, - 64, - kernel_size=kernel_size) - d3 = decoder_layer(d2, - e1, - 32, - kernel_size=kernel_size) - outputs = Conv2DTranspose(channels, - kernel_size=kernel_size, - strides=1, - activation='sigmoid', - padding='same')(d3) -``` - -```py - generator = Model(inputs, outputs, name=name) -``` - -```py - return generator -``` - -CycleGAN 的判别器类似于原始 GAN 判别器。 输入图像被下采样数次(在此示例中为 3 次)。 最后一层是`Dense`(1)层,它预测输入为实数的可能性。 除了不使用 IN 之外,每个层都类似于生成器的编码器层。 然而,在大图像中,用一个数字将图像计算为真实图像或伪图像会导致参数效率低下,并导致生成器的图像质量较差。 - -解决方案是使用 PatchGAN [6],该方法将图像划分为补丁网格,并使用标量值网格来预测补丁是真实概率。“图 7.1.11”显示了原始 GAN 判别器和`2 x 2` PatchGAN 判别器之间的比较: - -![](img/B14853_07_11.png) - -图 7.1.11:GAN 与 PatchGAN 判别器的比较 - -在此示例中,面片不重叠且在其边界处相遇。 但是,通常,补丁可能会重叠。 - -我们应该注意,PatchGAN 并没有在 CycleGAN 中引入一种新型的 GAN。 为了提高生成的图像质量,如果使用`2 x 2` PatchGAN,则没有四个输出可以区分,而没有一个输出可以区分。 损失函数没有变化。 从直觉上讲,这是有道理的,因为如果图像的每个面片或部分看起来都是真实的,则整个图像看起来会更加真实。 - -“图 7.1.12”显示了`tf.keras`中实现的判别器网络。 下图显示了判别器确定输入图像或色块为彩色 CIFAR10 图像的可能性: - -![](img/B14853_07_12.png) - -图 7.1.12:目标标识符`D[y]`在`tf.keras`中的实现。 PatchGAN 判别器显示在右侧 - -由于输出图像只有`32 x 32` RGB 时较小,因此表示该图像是真实的单个标量就足够了。 但是,当使用 PatchGAN 时,我们也会评估结果。“列表 7.1.3”显示了判别器的函数构建器: - -“列表 7.1.3”:`cyclegan-7.1.1.py` - -`tf.keras`中的判别器实现: - -```py -def build_discriminator(input_shape, - kernel_size=3, - patchgan=True, - name=None): - """The discriminator is a 4-layer encoder that outputs either - a 1-dim or a n x n-dim patch of probability that input is real -``` - -```py - Arguments: - input_shape (tuple): input shape - kernel_size (int): kernel size of decoder layers - patchgan (bool): whether the output is a patch - or just a 1-dim - name (string): name assigned to discriminator model -``` - -```py - Returns: - discriminator (Model): - """ -``` - -```py - inputs = Input(shape=input_shape) - x = encoder_layer(inputs, - 32, - kernel_size=kernel_size, - activation='leaky_relu', - instance_norm=False) - x = encoder_layer(x, - 64, - kernel_size=kernel_size, - activation='leaky_relu', - instance_norm=False) - x = encoder_layer(x, - 128, - kernel_size=kernel_size, - activation='leaky_relu', - instance_norm=False) - x = encoder_layer(x, - 256, - kernel_size=kernel_size, - strides=1, - activation='leaky_relu', - instance_norm=False) -``` - -```py - # if patchgan=True use nxn-dim output of probability - # else use 1-dim output of probability - if patchgan: - x = LeakyReLU(alpha=0.2)(x) - outputs = Conv2D(1, - kernel_size=kernel_size, - strides=2, - padding='same')(x) - else: - x = Flatten()(x) - x = Dense(1)(x) - outputs = Activation('linear')(x) -``` - -```py - discriminator = Model(inputs, outputs, name=name) -``` - -```py - return discriminator -``` - -使用生成器和判别器生成器,我们现在可以构建 CycleGAN。“列表 7.1.4”显示了构建器函数。 与上一节中的讨论一致,实例化了两个生成器`g_source = F`和`g_target = G`以及两个判别器`d_source = D[x]`和`d_target = D[y]`。 正向循环为`x' = F(G(x)) = reco_source = g_source(g_target(source_input))`。反向循环为`y' = G(F(y)) = reco_target = g_target(g_source (target_input))`。 - -对抗模型的输入是源数据和目标数据,而输出是`D[x]`和`D[y]`的输出以及重构的输入`x'`和`y'`。 在本示例中,由于由于灰度图像和彩色图像中通道数之间的差异,因此未使用身份网络。 对于 GAN 和循环一致性损失,我们分别使用建议的`λ1 = 1.0`和`λ2 = 10.0`损失权重。 与前几章中的 GAN 相似,我们使用 RMSprop 作为判别器的优化器,其学习率为`2e-4`,衰减率为`6e-8`。 对抗的学习率和衰退率是判别器的一半。 - -“列表 7.1.4”:`cyclegan-7.1.1.py` - -`tf.keras`中的 CycleGAN 构建器: - -```py -def build_cyclegan(shapes, - source_name='source', - target_name='target', - kernel_size=3, - patchgan=False, - identity=False - ): - """Build the CycleGAN -``` - -```py - 1) Build target and source discriminators - 2) Build target and source generators - 3) Build the adversarial network -``` - -```py - Arguments: - shapes (tuple): source and target shapes - source_name (string): string to be appended on dis/gen models - target_name (string): string to be appended on dis/gen models - kernel_size (int): kernel size for the encoder/decoder - or dis/gen models - patchgan (bool): whether to use patchgan on discriminator - identity (bool): whether to use identity loss -``` - -```py - Returns: - (list): 2 generator, 2 discriminator, - and 1 adversarial models - """ -``` - -```py - source_shape, target_shape = shapes - lr = 2e-4 - decay = 6e-8 - gt_name = "gen_" + target_name - gs_name = "gen_" + source_name - dt_name = "dis_" + target_name - ds_name = "dis_" + source_name -``` - -```py - # build target and source generators - g_target = build_generator(source_shape, - target_shape, - kernel_size=kernel_size, - name=gt_name) - g_source = build_generator(target_shape, - source_shape, - kernel_size=kernel_size, - name=gs_name) - print('---- TARGET GENERATOR ----') - g_target.summary() - print('---- SOURCE GENERATOR ----') - g_source.summary() -``` - -```py - # build target and source discriminators - d_target = build_discriminator(target_shape, - patchgan=patchgan, - kernel_size=kernel_size, - name=dt_name) - d_source = build_discriminator(source_shape, - patchgan=patchgan, - kernel_size=kernel_size, - name=ds_name) - print('---- TARGET DISCRIMINATOR ----') - d_target.summary() - print('---- SOURCE DISCRIMINATOR ----') - d_source.summary() -``` - -```py - optimizer = RMSprop(lr=lr, decay=decay) - d_target.compile(loss='mse', - optimizer=optimizer, - metrics=['accuracy']) - d_source.compile(loss='mse', - optimizer=optimizer, - metrics=['accuracy']) -``` - -```py - d_target.trainable = False - d_source.trainable = False -``` - -```py - # build the computational graph for the adversarial model - # forward cycle network and target discriminator - source_input = Input(shape=source_shape) - fake_target = g_target(source_input) - preal_target = d_target(fake_target) - reco_source = g_source(fake_target) -``` - -```py - # backward cycle network and source discriminator - target_input = Input(shape=target_shape) - fake_source = g_source(target_input) - preal_source = d_source(fake_source) - reco_target = g_target(fake_source) -``` - -```py - # if we use identity loss, add 2 extra loss terms - # and outputs - if identity: - iden_source = g_source(source_input) - iden_target = g_target(target_input) - loss = ['mse', 'mse', 'mae', 'mae', 'mae', 'mae'] - loss_weights = [1., 1., 10., 10., 0.5, 0.5] - inputs = [source_input, target_input] - outputs = [preal_source, - preal_target, - reco_source, - reco_target, - iden_source, - iden_target] - else: - loss = ['mse', 'mse', 'mae', 'mae'] - loss_weights = [1., 1., 10., 10.] - inputs = [source_input, target_input] - outputs = [preal_source, - preal_target, - reco_source, - reco_target] -``` - -```py - # build adversarial model - adv = Model(inputs, outputs, name='adversarial') - optimizer = RMSprop(lr=lr*0.5, decay=decay*0.5) - adv.compile(loss=loss, - loss_weights=loss_weights, - optimizer=optimizer, - metrics=['accuracy']) - print('---- ADVERSARIAL NETWORK ----') - adv.summary() -``` - -```py - return g_source, g_target, d_source, d_target, adv -``` - -我们遵循训练过程,我们可以从上一节中的“算法 7.1.1”中调用。“列表 7.1.5”显示了 CycleGAN 训练。 此训练与原始 GAN 之间的次要区别是有两个要优化的判别器。 但是,只有一种对抗模型需要优化。 对于每 2,000 步,生成器将保存预测的源图像和目标图像。 我们将的批量大小设为 32。我们也尝试了 1 的批量大小,但是输出质量几乎相同,并且需要花费更长的时间进行训练(批量为每个图像 43 ms,在 NVIDIA GTX 1060 上批量大小为 32 时,最大大小为每个图像 1 vs 3.6 ms) - -“列表 7.1.5”:`cyclegan-7.1.1.py` - -`tf.keras`中的 CycleGAN 训练例程: - -```py -def train_cyclegan(models, - data, - params, - test_params, - test_generator): - """ Trains the CycleGAN. - - 1) Train the target discriminator - 2) Train the source discriminator - 3) Train the forward and backward cyles of - adversarial networks -``` - -```py - Arguments: - models (Models): Source/Target Discriminator/Generator, - Adversarial Model - data (tuple): source and target training data - params (tuple): network parameters - test_params (tuple): test parameters - test_generator (function): used for generating - predicted target and source images - """ -``` - -```py - # the models - g_source, g_target, d_source, d_target, adv = models - # network parameters - batch_size, train_steps, patch, model_name = params - # train dataset - source_data, target_data, test_source_data, test_target_data\ - = data -``` - -```py - titles, dirs = test_params -``` - -```py - # the generator image is saved every 2000 steps - save_interval = 2000 - target_size = target_data.shape[0] - source_size = source_data.shape[0] -``` - -```py - # whether to use patchgan or not - if patch > 1: - d_patch = (patch, patch, 1) - valid = np.ones((batch_size,) + d_patch) - fake = np.zeros((batch_size,) + d_patch) - else: - valid = np.ones([batch_size, 1]) - fake = np.zeros([batch_size, 1]) -``` - -```py - valid_fake = np.concatenate((valid, fake)) - start_time = datetime.datetime.now() -``` - -```py - for step in range(train_steps): - # sample a batch of real target data - rand_indexes = np.random.randint(0, - target_size, - size=batch_size) - real_target = target_data[rand_indexes] -``` - -```py - # sample a batch of real source data - rand_indexes = np.random.randint(0, - source_size, - size=batch_size) - real_source = source_data[rand_indexes] - # generate a batch of fake target data fr real source data - fake_target = g_target.predict(real_source) -``` - -```py - # combine real and fake into one batch - x = np.concatenate((real_target, fake_target)) - # train the target discriminator using fake/real data - metrics = d_target.train_on_batch(x, valid_fake) - log = "%d: [d_target loss: %f]" % (step, metrics[0]) -``` - -```py - # generate a batch of fake source data fr real target data - fake_source = g_source.predict(real_target) - x = np.concatenate((real_source, fake_source)) - # train the source discriminator using fake/real data - metrics = d_source.train_on_batch(x, valid_fake) - log = "%s [d_source loss: %f]" % (log, metrics[0]) -``` - -```py - # train the adversarial network using forward and backward - # cycles. the generated fake source and target - # data attempts to trick the discriminators - x = [real_source, real_target] - y = [valid, valid, real_source, real_target] - metrics = adv.train_on_batch(x, y) - elapsed_time = datetime.datetime.now() - start_time - fmt = "%s [adv loss: %f] [time: %s]" - log = fmt % (log, metrics[0], elapsed_time) - print(log) - if (step + 1) % save_interval == 0: - test_generator((g_source, g_target), - (test_source_data, test_target_data), - step=step+1, - titles=titles, - dirs=dirs, - show=False) -``` - -```py - # save the models after training the generators - g_source.save(model_name + "-g_source.h5") - g_target.save(model_name + "-g_target.h5") -``` - -最后,在使用 CycleGAN 构建和训练函数之前,我们必须执行一些数据准备。 模块`cifar10_utils.py`和`other_ utils.py`加载`CIFAR10`训练和测试数据。 有关这两个文件的详细信息,请参考源代码。 加载后,将训练图像和测试图像转换为灰度,以生成源数据和测试源数据。 - -“列表 7.1.6”显示了 CycleGAN 如何用于构建和训练用于灰度图像着色的生成器网络(`g_target`)。 由于 CycleGAN 是对称的,因此我们还构建并训练了第二个生成器网络(`g_source`),该网络可以将颜色转换为灰度。 训练了两个 CycleGAN 着色网络。 第一种使用标量输出类似于原始 GAN 的判别器,第二种使用`2 x 2` PatchGAN。 - -“列表 7.1.6”:`cyclegan-7.1.1.py` - -CycleGAN 用于着色: - -```py -def graycifar10_cross_colorcifar10(g_models=None): - """Build and train a CycleGAN that can do - grayscale <--> color cifar10 images - """ -``` - -```py - model_name = 'cyclegan_cifar10' - batch_size = 32 - train_steps = 100000 - patchgan = True - kernel_size = 3 - postfix = ('%dp' % kernel_size) \ - if patchgan else ('%d' % kernel_size) -``` - -```py - data, shapes = cifar10_utils.load_data() - source_data, _, test_source_data, test_target_data = data - titles = ('CIFAR10 predicted source images.', - 'CIFAR10 predicted target images.', - 'CIFAR10 reconstructed source images.', - 'CIFAR10 reconstructed target images.') - dirs = ('cifar10_source-%s' % postfix, \ - 'cifar10_target-%s' % postfix) -``` - -```py - # generate predicted target(color) and source(gray) images - if g_models is not None: - g_source, g_target = g_models - other_utils.test_generator((g_source, g_target), - (test_source_data, \ - test_target_data), - step=0, - titles=titles, - dirs=dirs, - show=True) - return -``` - -```py - # build the cyclegan for cifar10 colorization - models = build_cyclegan(shapes, - "gray-%s" % postfix, - "color-%s" % postfix, - kernel_size=kernel_size, - patchgan=patchgan) - # patch size is divided by 2^n since we downscaled the input - # in the discriminator by 2^n (ie. we use strides=2 n times) - patch = int(source_data.shape[1] / 2**4) if patchgan else 1 - params = (batch_size, train_steps, patch, model_name) - test_params = (titles, dirs) - # train the cyclegan - train_cyclegan(models, - data, - params, - test_params, - other_utils.test_generator) -``` - -在的下一部分中,我们将检查 CycleGAN 的生成器输出以进行着色。 - -## CycleGAN 的生成器输出 - -“图 7.1.13”显示 CycleGAN 的着色结果。 源图像来自测试数据集: - -![](img/B14853_07_13.png) - -图 7.1.13:使用不同技术进行着色。 显示的是基本事实,使用自编码器的着色(第 3 章,自编码器),使用带有原始 GAN 判别器的 CycleGAN 进行着色,以及使用带有 PatchGAN 判别器的 CycleGAN 进行着色。 彩色效果最佳。 原始彩色照片可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter7-cross-domain-gan/README.md)中找到。 - -为了进行比较,我们使用第 3 章,“自编码器”中描述的普通自编码器显示了地面真实情况和着色结果。 通常,所有彩色图像在感觉上都是可接受的。 总体而言,似乎每种着色技术都有自己的优点和缺点。 所有着色方法与天空和车辆的正确颜色不一致。 - -例如,平面背景(第三行,第二列)中的天空为白色。 自编码器没错,但是 CycleGAN 认为它是浅棕色或蓝色。 - -对于第六行第六列,暗海上的船天空阴沉,但自编码器将其涂成蓝色和蓝色,而 CycleGAN 将其涂成蓝色和白色,而没有 PatchGAN。 两种预测在现实世界中都是有意义的。 同时,使用 PatchGAN 对 CycleGAN 的预测与基本事实相似。 在倒数第二行和第二列上,没有方法能够预测汽车的红色。 在动物身上,CycleGAN 的两种口味都具有接近真实情况的颜色。 - -由于 CycleGAN 是对称的,因此它还能在给定彩色图像的情况下预测灰度图像。“图 7.1.14”显示了两个 CycleGAN 变体执行的颜色到灰度转换。 目标图像来自测试数据集。 除了某些图像的灰度阴影存在细微差异外,这些预测通常是准确的。 - -![](img/B14853_07_14.png) - -图 7.1.14:颜色(来自图 7.1.9)到 CycleGAN 的灰度转换 - -要训​​练 CycleGAN 进行着色,命令是: - -```py -python3 cyclegan-7.1.1.py -c -``` - -读者可以使用带有 PatchGAN 的 CycleGAN 预训练模型来运行图像转换: - -```py -python3 cyclegan-7.1.1.py --cifar10_g_source=cyclegan_cifar10-g_source.h5 ---cifar10_g_target=cyclegan_cifar10-g_target.h5 -``` - -在本节中,我们演示了 CycleGAN 在着色上的一种实际应用。 在下一部分中,我们将在更具挑战性的数据集上训练 CycleGAN。 源域 MNIST 与目标域 SVHN 数据集有很大的不同[1]。 - -## MNIST 和 SVHN 数据集上的 CycleGAN - -我们现在要解决一个更具挑战性的问题。 假设我们使用 MNIST 灰度数字作为源数据,并且我们想从 *SVHN* [1]中借鉴样式,这是我们的目标数据。 每个域中的样本数据显示在“图 7.1.15”中: - -![](img/B14853_07_15.png) - -图 7.1.15:两个未对齐数据的不同域。 原始彩色照片可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter7-cross-domain-gan/README.md)中找到。 - -我们可以重用上一节中讨论的 CycleGAN 的所有构建和训练函数,以执行样式迁移。 唯一的区别是,我们必须添加用于加载 MNIST 和 SVHN 数据的例程。 SVHN 数据集可在[这个页面](http://ufldl.stanford.edu/housenumbers/)中找到。 - -我们介绍`mnist_svhn_utils.py`模块来帮助我们完成此任务。“列表 7.1.7”显示了针对跨域迁移的 CycleGAN 的初始化和训练。 - -CycleGAN 结构与上一部分相同,不同之处在于我们使用的核大小为 5,因为两个域完全不同。 - -“列表 7.1.7”:`cyclegan-7.1.1.py` - -CycleGAN 用于 MNIST 和 SVHN 之间的跨域样式迁移: - -```py -def mnist_cross_svhn(g_models=None): - """Build and train a CycleGAN that can do mnist <--> svhn - """ -``` - -```py - model_name = 'cyclegan_mnist_svhn' - batch_size = 32 - train_steps = 100000 - patchgan = True - kernel_size = 5 - postfix = ('%dp' % kernel_size) \ - if patchgan else ('%d' % kernel_size) -``` - -```py - data, shapes = mnist_svhn_utils.load_data() - source_data, _, test_source_data, test_target_data = data - titles = ('MNIST predicted source images.', - 'SVHN predicted target images.', - 'MNIST reconstructed source images.', - 'SVHN reconstructed target images.') - dirs = ('mnist_source-%s' \ - % postfix, 'svhn_target-%s' % postfix) -``` - -```py - # generate predicted target(svhn) and source(mnist) images - if g_models is not None: - g_source, g_target = g_models - other_utils.test_generator((g_source, g_target), - (test_source_data, \ - test_target_data), - step=0, - titles=titles, - dirs=dirs, - show=True) - return -``` - -```py - # build the cyclegan for mnist cross svhn - models = build_cyclegan(shapes, - "mnist-%s" % postfix, - "svhn-%s" % postfix, - kernel_size=kernel_size, - patchgan=patchgan) - # patch size is divided by 2^n since we downscaled the input - # in the discriminator by 2^n (ie. we use strides=2 n times) - patch = int(source_data.shape[1] / 2**4) if patchgan else 1 - params = (batch_size, train_steps, patch, model_name) - test_params = (titles, dirs) - # train the cyclegan - train_cyclegan(models, - data, - params, - test_params, - other_utils.test_generator) -``` - -将 MNIST 从测试数据集迁移到 SVHN 的结果显示在“图 7.1.16”中。 生成的图像具有样式的 SVHN,但是数字未完全传送。 例如,在第四行上,数字 3、1 和 3 由 CycleGAN 进行样式化。 - -但是,在第三行中,不带有和带有 PatchGAN 的 CycleGAN 的数字 9、6 和 6 分别设置为 0、6、01、0、65 和 68: - -![](img/B14853_07_16.png) - -图 7.1.16:测试数据从 MNIST 域到 SVHN 的样式迁移。 原始彩色照片可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter7-cross-domain-gan/README.md)中找到。 - -向后循环的结果为“图 7.1.17”中所示的。 在这种情况下,目标图像来自 SVHN 测试数据集。 生成的图像具有 MNIST 的样式,但是数字没有正确翻译。 例如,在第一行中,对于不带和带有 PatchGAN 的 CycleGAN,数字 5、2 和 210 分别被样式化为 7、7、8、3、3 和 1: - -![](img/B14853_07_17.png) - -图 7.1.17:测试数据从 SVHN 域到 MNIST 的样式迁移。 原始彩色照片可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter7-cross-domain-gan/README.md)中找到。 - -在 PatchGAN 的情况下,假设预测的 MNIST 数字被限制为一位,则输出 1 是可以理解的。 有以某种方式正确的预测,例如在第二行中,不使用 PatchGAN 的 CycleGAN 将 SVHN 数字的最后三列 6、3 和 4 转换为 6、3 和 6。 但是,CycleGAN 两种版本的输出始终是个位数且可识别。 - -从 MNIST 到 SVHN 的转换中出现的问题称为“标签翻转”[8],其中源域中的数字转换为目标域中的另一个数字。 尽管 CycleGAN 的预测是周期一致的,但它们不一定是语义一致的。 在翻译过程中数字的含义会丢失。 - -为了解决这个问题, *Hoffman* [8]引入了一种改进的 CycleGAN,称为**循环一致性对抗域自适应**(**CyCADA**)。 不同之处在于,附加的语义损失项可确保预测不仅周期一致,而且语义一致。 - -“图 7.1.18”显示 CycleGAN 在正向循环中重建 MNIST 数字。 重建的 MNIST 数字几乎与源 MNIST 数字相同: - -![](img/B14853_07_18.png) - -图 7.1.18:带有 MNIST 上的 PatchGAN 的 CycleGAN(源)到 SVHN(目标)的前向周期。 重建的源类似于原始源。 原始彩色照片可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter7-cross-domain-gan/README.md)中找到。 - -“图 7.1.19”显示了 CycleGAN 在向后周期中重构 SVHN 数字的过程: - -![](img/B14853_07_19.png) - -图 7.1.19:带有 MNIST 上的 PatchGAN 的 CycleGAN 与 SVHN(目标)的反向循环。 重建的目标与原始目标并不完全相似。 原始彩色照片可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/blob/master/chapter7-cross-domain-gan/README.md)中找到。 - -在“图 7.1.3”中,CycleGAN 被描述为具有周期一致性。 换句话说,给定源`x`,CycleGAN 将正向循环中的源重构为`x'`。 另外,在给定目标`y`的情况下,CycleGAN 在反向循环中将目标重构为`y'`。 - -重建了许多目标图像。 有些数字显然是相同的,例如最后两列(3 和 4)中的第二行,而有些数字却是相同的但是模糊的,例如前两列列中的第一行(5 和 2)。 尽管样式仍像第二行一样,但在前两列(从 33 和 6 到 1 以及无法识别的数字)中,有些数字会转换为另一数字。 - -要将 MNIST 的 CycleGAN 训练为 SVHN,命令为: - -```py -python3 cyclegan-7.1.1.py -m -``` - -鼓励读者使用带有 PatchGAN 的 CycleGAN 预训练模型来运行图像翻译: - -```py -python3 cyclegan-7.1.1.py --mnist_svhn_g_source=cyclegan_mnist_svhn-g_ source.h5 --mnist_svhn_g_target=cyclegan_mnist_svhn-g_target.h5 -``` - -到目前为止,我们只看到了 CycleGAN 的两个实际应用。 两者都在小型数据集上进行了演示,以强调可重复性的概念。 如本章前面所述,CycleGAN 还有许多其他实际应用。 我们在这里介绍的 CycleGAN 可以作为分辨率更高的图像转换的基础。 - -# 2\. 总结 - -在本章中,我们讨论了 CycleGAN 作为可用于图像翻译的算法。 在 CycleGAN 中,源数据和目标数据不一定要对齐。 我们展示了两个示例,*灰度 ↔ 颜色*和 *MNIST ↔ SVHN* ,尽管 CycleGAN 可以执行许多其他可能的图像转换 。 - -在下一章中,我们将着手另一种生成模型,即**变分自编码器**(**VAE**)。 VAE 具有类似的学习目标–如何生成新图像(数据)。 他们专注于学习建模为高斯分布的潜在向量。 我们将以有条件的 VAE 和解开 VAE 中的潜在表示形式来证明 GAN 解决的问题中的其他相似之处。 - -# 3\. 参考 - -1. `Yuval Netzer et al.: Reading Digits in Natural Images with Unsupervised Feature Learning. NIPS workshop on deep learning and unsupervised feature learning. Vol. 2011. No. 2. 2011 (https://www-cs.stanford.edu/~twangcat/papers/nips2011_housenumbers.pdf).` -1. `Zhu-Jun-Yan et al.: Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. 2017 IEEE International Conference on Computer Vision (ICCV). IEEE, 2017 (http://openaccess.thecvf.com/content_ICCV_2017/papers/Zhu_Unpaired_Image-To-Image_Translation_ICCV_2017_paper.pdf).` -1. `Phillip Isola et al.: Image-to-Image Translation with Conditional Adversarial Networks. 2017 IEEE Conference on Computer Vision and Pattern Recognition (CVPR). IEEE, 2017 (http://openaccess.thecvf.com/content_cvpr_2017/papers/Isola_Image-To-Image_Translation_With_CVPR_2017_paper.pdf).` -1. `Mehdi Mirza and Simon Osindero. Conditional Generative Adversarial Nets. arXiv preprint arXiv:1411.1784, 2014 (https://arxiv.org/pdf/1411.1784.pdf).` -1. `Xudong Mao et al.: Least Squares Generative Adversarial Networks. 2017 IEEE International Conference on Computer Vision (ICCV). IEEE, 2017 (http://openaccess.thecvf.com/content_ICCV_2017/papers/Mao_Least_Squares_Generative_ICCV_2017_paper.pdf).` -1. `Chuan Li and Michael Wand. Precomputed Real-Time Texture Synthesis with Markovian Generative Adversarial Networks. European Conference on Computer Vision. Springer, Cham, 2016 (https://arxiv.org/pdf/1604.04382.pdf).` -1. `Olaf Ronneberger, Philipp Fischer, and Thomas Brox. U-Net: Convolutional Networks for Biomedical Image Segmentation. International Conference on Medical image computing and computer-assisted intervention. Springer, Cham, 2015 (https://arxiv.org/pdf/1505.04597.pdf).` -1. `Judy Hoffman et al.: CyCADA: Cycle-Consistent Adversarial Domain Adaptation. arXiv preprint arXiv:1711.03213, 2017 (https://arxiv.org/pdf/1711.03213.pdf).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/08.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/08.md deleted file mode 100644 index c45a273c..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/08.md +++ /dev/null @@ -1,699 +0,0 @@ -# 八、变分自编码器(VAE) - -与我们在之前的章节中讨论过的**生成对抗网络**(**GAN**)类似,**变分自编码器**(**VAE**)[1] 属于生成模型家族。 VAE 的生成器能够在导航其连续潜在空间的同时产生有意义的输出。 通过潜向量探索解码器输出的可能属性。 - -在 GAN 中,重点在于如何得出近似输入分布的模型。 VAE 尝试对可解码的连续潜在空间中的输入分布进行建模。 这是 GAN 与 VAE 相比能够生成更真实信号的可能的潜在原因之一。 例如,在图像生成中,GAN 可以生成看起来更逼真的图像,而相比之下,VAE 生成的图像清晰度较差。 - -在 VAE 中,重点在于潜在代码的变分推理。 因此,VAE 为潜在变量的学习和有效贝叶斯推理提供了合适的框架。 例如,带有解缠结表示的 VAE 可以将潜在代码重用于迁移学习。 - -在结构上,VAE 与自编码器相似。 它也由编码器(也称为识别或推理模型)和解码器(也称为生成模型)组成。 VAE 和自编码器都试图在学习潜向量的同时重建输入数据。 - -但是,与自编码器不同,VAE 的潜在空间是连续的,并且解码器本身被用作生成模型。 - -在前面各章中讨论的 GAN 讨论中,也可以对 VAE 的解码器进行调整。 例如,在 MNIST 数据集中,我们能够指定一个给定的单热向量产生的数字。 这种有条件的 VAE 类别称为 CVAE [2]。 也可以通过在损失函数中包含正则化超参数来解开 VAE 潜向量。 这称为 β-VAE [5]。 例如,在 MNIST 中,我们能够隔离确定每个数字的粗细或倾斜角度的潜向量。 本章的目的是介绍: - -* VAE 的原理 -* 了解重新参数化技巧,有助于在 VAE 优化中使用随机梯度下降 -* 有条件的 VAE(CVAE)和 β-VAE 的原理 -* 了解如何使用`tf.keras`实现 VAE - -我们将从谈论 VAE 的基本原理开始。 - -# 1\. VAE 原理 - -在生成模型中,我们经常对使用神经网络来逼近输入的真实分布感兴趣: - -![](img/B14853_08_003.png) (Equation 8.1.1) - -在前面的等式中,`θ`表示训练期间确定的参数。 例如,在名人面孔数据集的上下文中,这等效于找到可以绘制面孔的分布。 同样,在 MNIST 数据集中,此分布可以生成可识别的手写数字。 - -在机器学习中,为了执行特定级别的推理,我们有兴趣寻找`P[θ](x, z)`,这是输入`x`和潜在变量`z`之间的联合分布。 潜在变量不是数据集的一部分,而是对可从输入中观察到的某些属性进行编码。 在名人面孔的背景下,这些可能是面部表情,发型,头发颜色,性别等。 在 MNIST 数据集中,潜在变量可以表示数字和书写样式。 - -`P[θ](x, z)`实际上是输入数据点及其属性的分布。 `P[θ](x)`可以从边际分布计算得出: - -![](img/B14853_08_010.png) (Equation 8.1.2) - -换句话说,考虑所有可能的属性,我们最终得到描述输入的分布。 在名人面孔中,如果考虑所有面部表情,发型,头发颜色和性别,将恢复描述名人面孔的分布。 在 MNIST 数据集中,如果考虑所有可能的数字,书写风格等,我们以手写数字的分布来结束。 - -问题在于“公式 8.1.2”很难处理。 该方程式没有解析形式或有效的估计量。 它的参数无法微分。 因此,通过神经网络进行优化是不可行的。 - -使用贝叶斯定理,我们可以找到“公式 8.1.2”的替代表达式: - -![](img/B14853_08_011.png) (Equation 8.1.3) - -`P(z)`是`z`的先验分布。 它不以任何观察为条件。 如果`z`是离散的,而`P[θ](x | z)`是高斯分布,则`P[θ](x)`是高斯的混合。 如果`z`是连续的,则`P[θ](x)`是高斯的无限混合。 - -实际上,如果我们尝试在没有合适的损失函数的情况下建立一个近似`P[θ](x | z)`的神经网络,它将忽略`z`得出一个简单的解`P[θ](x | z) = P[θ](x)`。 因此,“公式 8.1.3”无法为我们提供`P[θ](x)`的良好估计。 或者,“公式 8.1.2”也可以表示为: - -![](img/B14853_08_024.png) (Equation 8.1.4) - -但是,`P[θ](z | x)`也很棘手。 VAE 的目标是在给定输入的情况下,找到一种可预测的分布,该分布易于估计`P[θ](z | x)`,即潜在属性`z`的条件分布的估计。 - -## 变分推理 - -为了使易于处理,VAE 引入了变化推理模型(编码器): - -![](img/B14853_08_030.png) (Equation 8.1.5) - -`Q[φ](z | x)`提供了`P[θ](z | x)`的良好估计。 它既参数化又易于处理。 `Q[φ](z | x)`可以通过优化参数`φ`由深度神经网络近似。 通常,`Q[φ](z | x)`被选择为多元高斯: - -![](img/B14853_08_036.png) (Equation 8.1.6) - -均值`μ(x)`和标准差`σ(x)`均由编码器神经网络使用输入数据点计算得出。 对角线矩阵表示`z`的元素是独立的。 - -在下一节中,我们将求解 VAE 的核心方程。 核心方程式将引导我们找到一种优化算法,该算法将帮助我们确定推理模型的参数。 - -## 核心方程 - -推理模型`Q[φ](z | x)`从输入`x`生成潜向量`z`。 `Q[φ](z | x)`似于自编码器模型中的编码器。 另一方面,从潜在代码`z`重构输入。 `P[θ](x | z)`的作用类似于自编码器模型中的解码器。 要估计`P[θ](x)`,我们必须确定其与`Q[φ](z | x)`和`P[θ](x | z)`的关系。 - -如果`Q[φ](z | x)`是`P[θ](z | x)`的估计值,则 **Kullback-Leibler**(**KL**)的差异决定了这两个条件密度之间的距离: - -![](img/B14853_08_052.png) (Equation 8.1.7) - -使用贝叶斯定理: - -![](img/B14853_08_053.png) (Equation 8.1.8) - -在“公式 8.1.7”中: - -![](img/B14853_08_054.png) (Equation 8.1.9) - -由于`log P[θ](x)`不依赖于`z ~ Q`,因此可能会超出预期。 重新排列“公式 8.1.9”并认识到: - -![](img/B14853_08_057.png),其结果是: - -![](img/B14853_08_058.png) (Equation 8.1.10) - -“公式 8.1.10”是 VAE 的核心。 左侧是项`P[θ](x)`,由于`Q[φ](z | x)`与真实`P[θ](z | x)`的距离,我们使误差最小化。 我们可以记得,的对数不会更改最大值(或最小值)的位置。 给定提供`P[θ](z | x)`良好估计的推断模型,`D[KL](Q[φ](z | x) || P[θ](z | x)`大约为零。 - -右边的第一项`P[θ](x | z)`类似于解码器,该解码器从推理模型中抽取样本以重建输入。 - -第二个项是另一个距离。 这次是在`Q[φ](z | x)`和先前的`P[θ](z)`之间。 “公式 8.1.10”的左侧也称为**变异下界**或**证据下界**(**ELBO**)。 由于 KL 始终为正,因此 ELBO 是`log P[θ](x)`的下限。 通过优化神经网络的参数`φ`和`θ`来最大化 ELBO 意味着: - -* 在将`z`中的`x`属性编码时,`D[KL](Q[φ](z | x) || P[θ](z | x) -> 0`或推理模型变得更好。 -* 右边的`log P[θ](x | z)`最大化了“公式 8.1.10”或解码器模型在从潜在向量`z`重构`x`方面变得更好。 -* 在下一节中,我们将利用“公式 8.1.10”的结构来确定推理模型(编码器)和解码器的损失函数。 - -## 优化 - -“公式 8.1.10”的右侧具有有关 VAE 的`loss`函数的两个重要信息。 解码器项`E[z~Q] [log P[θ](x | z)]`表示生成器从推理模型的输出中提取`z`个样本,以重建输入。 使最大化是指我们将**重构损失**和`L_R`降到最低。 如果假设图像(数据)分布为高斯分布,则可以使用 MSE。 - -如果每个像素(数据)都被认为是伯努利分布,那么损失函数就是二进制互熵。 - -第二项`-D[KL](Q[φ](z | x) || P[θ](z))`易于评估。 根据“公式 8.1.6”,`Q[φ]`是高斯分布。 通常,`P[θ](z) = P(z) = N(0, 1)`也是平均值为零且标准差等于 1.0 的高斯。 在“公式 8.1.11”中,我们看到 KL 项简化为: - -![](img/B14853_08_082.png) (Equation 8.1.11) - -其中`J`是`z`的维。 `μ[j]`和`σ[j]`都是通过推理模型计算的`x`的函数。 要最大化:`-D[KL]`,`σ[j] -> 1`和`μ[j] -> 9`。 `P(z) = N(0, 1)`的选择源于各向同性单位高斯的性质,在具有适当函数的情况下,它可以变形为任意分布[6]。 - -根据“公式 8.1.11”,KL 损失`L_KL`简称为`D[KL]`。 - -总之,在“公式 8.1.12”中将 VAE `loss`函数定义为: - -![](img/B14853_08_094.png) (Equation 8.1.12) - -在给定编码器和解码器模型的情况下,在我们可以构建和训练 VAE(随机采样块,生成潜在属性)之前,还需要解决一个问题。 在下一节中,我们将讨论此问题以及如何使用重新参数化技巧解决它。 - -## 重新参数化技巧 - -“图 8.1.1”的左侧显示了 VAE 网络。 编码器获取输入`x`,并估计潜向量`z`的多元高斯分布的平均值`μ`和标准差`σ`。 解码器从潜向量`z`中提取样本,以将输入重构为`x_tilde`。 这似乎很简单,直到在反向传播期间发生梯度更新为止: - -![](img/B14853_08_01.png) - -图 8.1.1:带有和不带有重新参数化技巧的 VAE 网络 - -反向传播梯度将不会通过随机**采样**块。 尽管具有用于神经网络的随机输入是可以的,但梯度不可能穿过随机层。 - -解决此问题的方法是将**采样**处理作为输入,如“图 8.1.1”右侧所示。 然后,将样本计算为: - -![](img/B14853_08_101.png) (Equation 8.1.13) - -如果`ε`和`σ`以向量格式表示,则`εσ`是逐元素乘法。 使用“公式 8.1.13”,看起来好像采样直接来自潜在空间一样。 这项技术被称为*重新参数化技巧*。 - -现在,在输入端发生*采样*时,可以使用熟悉的优化算法(例如 SGD,Adam 或 RMSProp)来训练 VAE 网络。 - -在讨论如何在`tf.keras`中实现 VAE 之前,让我们首先展示如何测试经过训练的解码器。 - -## 解码器测试 - -在训练了 VAE 网络之后,可以丢弃推理模型,包括加法和乘法运算符。 为了生成新的有意义的输出,请从用于生成`ε`的高斯分布中抽取样本。“图 8.1.2”向我们展示了解码器的测试设置: - -![](img/B14853_08_02.png) - -图 8.1.2:解码器测试设置 - -通过重新参数化技巧解决了 VAE 上的最后一个问题,我们现在可以在`tf.keras`中实现和训练变分自编码器。 - -## ALAS 与 Keras - -VAE 的结构类似于典型的自编码器。 区别主要在于重新参数化技巧中的高斯随机变量的采样。“列表 8.1.1”显示了使用 MLP 实现的编码器,解码器和 VAE。 - -[此代码也已添加到官方 Keras GitHub 存储库中](https://github.com/keras-team/keras/blob/master/examples/variational_autoencoder.py)。 - -为便于显示潜在代码,将`z`的维设置为 2。编码器仅是两层 MLP,第二层生成均值和对数方差。 对数方差的使用是为了简化 KL 损失和重新参数化技巧的计算。 编码器的第三个输出是使用重新参数化技巧进行的`z`采样。 我们应该注意,在采样函数`exp(0.5 log σ²) = sqrt(σ²) = σ`中,因为`σ > 0`假定它是高斯分布的标准差。 - -解码器也是两层 MLP,它采用`z`的样本来近似输入。 编码器和解码器均使用大小为 512 的中间尺寸。 - -VAE 网络只是将编码器和解码器连接在一起。 `loss`函数是*重建损失*和 *KL 损失*的总和。 在默认的 Adam 优化器上,VAE 网络具有良好的效果。 VAE 网络中的参数总数为 807,700。 - -VAE MLP 的 Keras 代码具有预训练的权重。 要测试,我们需要运行: - -```py -python3 vae-mlp-mnist-8.1.1.py --weights=vae_mlp_mnist.tf -``` - -[完整的代码可以在以下链接中找到](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -“列表 8.1.1”:`vae-mlp-mnist-8.1.1.py` - -```py -# reparameterization trick -# instead of sampling from Q(z|X), sample eps = N(0,I) -# z = z_mean + sqrt(var)*eps -def sampling(args): - """Reparameterization trick by sampling - fr an isotropic unit Gaussian. -``` - -```py - # Arguments: - args (tensor): mean and log of variance of Q(z|X) -``` - -```py - # Returns: - z (tensor): sampled latent vector - """ -``` - -```py - z_mean, z_log_var = args - # K is the keras backend - batch = K.shape(z_mean)[0] - dim = K.int_shape(z_mean)[1] - # by default, random_normal has mean=0 and std=1.0 - epsilon = K.random_normal(shape=(batch, dim)) - return z_mean + K.exp(0.5 * z_log_var) * epsilon -``` - -```py -# MNIST dataset -(x_train, y_train), (x_test, y_test) = mnist.load_data() -``` - -```py -image_size = x_train.shape[1] -original_dim = image_size * image_size -x_train = np.reshape(x_train, [-1, original_dim]) -x_test = np.reshape(x_test, [-1, original_dim]) -x_train = x_train.astype('float32') / 255 -x_test = x_test.astype('float32') / 255 -``` - -```py -# network parameters -input_shape = (original_dim, ) -intermediate_dim = 512 -batch_size = 128 -latent_dim = 2 -epochs = 50 -``` - -```py -# VAE model = encoder + decoder -# build encoder model -inputs = Input(shape=input_shape, name='encoder_input') -x = Dense(intermediate_dim, activation='relu')(inputs) -z_mean = Dense(latent_dim, name='z_mean')(x) -z_log_var = Dense(latent_dim, name='z_log_var')(x) -``` - -```py -# use reparameterization trick to push the sampling out as input -# note that "output_shape" isn't necessary -# with the TensorFlow backend -z = Lambda(sampling, - output_shape=(latent_dim,), - name='z')([z_mean, z_log_var]) -``` - -```py -# instantiate encoder model -encoder = Model(inputs, [z_mean, z_log_var, z], name='encoder') -``` - -```py -# build decoder model -latent_inputs = Input(shape=(latent_dim,), name='z_sampling') -x = Dense(intermediate_dim, activation='relu')(latent_inputs) -outputs = Dense(original_dim, activation='sigmoid')(x) -``` - -```py -# instantiate decoder model -decoder = Model(latent_inputs, outputs, name='decoder') -# instantiate VAE model -outputs = decoder(encoder(inputs)[2]) -vae = Model(inputs, outputs, name='vae_mlp') -``` - -```py -if __name__ == '__main__': - parser = argparse.ArgumentParser() - help_ = "Load tf model trained weights" - parser.add_argument("-w", "--weights", help=help_) - help_ = "Use binary cross entropy instead of mse (default)" - parser.add_argument("--bce", help=help_, action='store_true') - args = parser.parse_args() - models = (encoder, decoder) - data = (x_test, y_test) -``` - -```py - # VAE loss = mse_loss or xent_loss + kl_loss - if args.bce: - reconstruction_loss = binary_crossentropy(inputs, - outputs) - else: - reconstruction_loss = mse(inputs, outputs) - - reconstruction_loss *= original_dim - kl_loss = 1 + z_log_var - K.square(z_mean) - K.exp(z_log_var) - kl_loss = K.sum(kl_loss, axis=-1) - kl_loss *= -0.5 - vae_loss = K.mean(reconstruction_loss + kl_loss) - vae.add_loss(vae_loss) - vae.compile(optimizer='adam') -``` - -“图 8.1.3”显示了编码器模型,它是一个 MLP,具有两个输出,即潜向量的均值和方差。 lambda 函数实现了重新参数化技巧,将随机潜在代码的采样推送到 VAE 网络之外: - -![A screenshot of a cell phone Description automatically generated](img/B14853_08_03.png) - -图 8.1.3:VAE MLP 的编码器模型 - -“图 8.1.4”显示了解码器模型。 2 维输入来自 lambda 函数。 输出是重构的 MNIST 数字: - -![A screenshot of a cell phone Description automatically generated](img/B14853_08_04.png) - -图 8.1.4:VAE MLP 的解码器模型 - -“图 8.1.5”显示了完整的 VAE 模型。 通过将编码器和解码器模型结合在一起制成: - -![](img/B14853_08_05.png) - -图 8.1.5:使用 MLP 的 VAE 模型 - -“图 8.1.6”显示了使用`plot_results()`在 50 个周期后潜向量的连续空间。 为简单起见,此函数未在此处显示,但可以在`vae-mlp-mnist-8.1.1.py`的其余代码中找到。 该函数绘制两个图像,即测试数据集标签(“图 8.1.6”)和样本生成的数字(“图 8.1.7”),这两个图像都是`z`的函数。 这两个图都说明了潜在向量如何确定所生成数字的属性: - -![A close up of a plant Description automatically generated](img/B14853_08_06.png) - -图 8.1.6:MNIST 数字标签作为测试数据集(VAE MLP)的潜在向量平均值的函数。 原始图像可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter8-vae)中找到。 - -浏览时,连续空格始终会产生与 MNIST 数字相似的输出。 例如,数字 9 的区域接近数字 7 的区域。从中心附近的 9 移动到左下角会将数字变形为 7。从中心向上移动会将生成的数字从 3 更改为 5,最后变为 0.数字的变形在“图 8.1.7”中更明显,这是解释“图 8.1.6”的另一种方式。 - -在“图 8.1.7”中,显示生成器输出。 显示了潜在空间中数字的分布。 可以观察到所有数字都被表示。 由于中心附近分布密集,因此变化在中间迅速,在平均值较高的区域则缓慢。 我们需要记住,“图 8.1.7”是“图 8.1.6”的反映。 例如,数字 0 在两个图的左上象限中,而数字 1 在右下象限中。 - -“图 8.1.7”中存在一些无法识别的数字,尤其是在右上象限中。 从“图 8.1.6”可以看出,该区域大部分是空的,并且远离中心: - -![](img/B14853_08_07.png) - -图 8.1.7:根据潜在向量平均值(VAE MLP)生成的数字。 为了便于解释,均值的范围类似于图 8.1.6 - -在本节中,我们演示了如何在 MLP 中实现 VAE。 我们还解释了导航潜在空间的结果。 在的下一部分中,我们将使用 CNN 实现相同的 VAE。 - -## 带有 CNN 的 AE - -在原始论文《自编码变分贝叶斯》[1]中,使用 MLP 来实现 VAE 网络,这与我们在上一节中介绍的类似。 在本节中,我们将证明使用 CNN 将显着提高所产生数字的质量,并将参数数量显着减少至 134,165。 - -“列表 8.1.3”显示了编码器,解码器和 VAE 网络。 [该代码也被添加到了官方的 Keras GitHub 存储库中](https://github.com/keras-team/keras/blob/master/examples/variational_autoencoder_deconv.py)。 - -为简洁起见,不再显示与 MLP VAE 类似的某些代码行。 编码器由两层 CNN 和两层 MLP 组成,以生成潜在代码。 编码器的输出结构与上一节中看到的 MLP 实现类似。 解码器由一层`Dense`和三层转置的 CNN 组成。 - -VAE CNN 的 Keras 代码具有预训练的权重。 要测试,我们需要运行: - -```py -python3 vae-cnn-mnist-8.1.2.py --weights=vae_cnn_mnist.tf -``` - -“列表 8.1.3”:`vae-cnn-mnist-8.1.2.py` - -使用 CNN 层的`tf.keras`中的 VAE: - -```py -# network parameters -input_shape = (image_size, image_size, 1) -batch_size = 128 -kernel_size = 3 -filters = 16 -latent_dim = 2 -epochs = 30 -``` - -```py -# VAE model = encoder + decoder -# build encoder model -inputs = Input(shape=input_shape, name='encoder_input') -x = inputs -for i in range(2): - filters *= 2 - x = Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu', - strides=2, - padding='same')(x) -``` - -```py -# shape info needed to build decoder model -shape = K.int_shape(x) -``` - -```py -# generate latent vector Q(z|X) -x = Flatten()(x) -x = Dense(16, activation='relu')(x) -z_mean = Dense(latent_dim, name='z_mean')(x) -z_log_var = Dense(latent_dim, name='z_log_var')(x) -``` - -```py -# use reparameterization trick to push the sampling out as input -# note that "output_shape" isn't necessary -# with the TensorFlow backend -z = Lambda(sampling, - output_shape=(latent_dim,), - name='z')([z_mean, z_log_var]) -``` - -```py -# instantiate encoder model -encoder = Model(inputs, [z_mean, z_log_var, z], name='encoder') -``` - -```py -# build decoder model -latent_inputs = Input(shape=(latent_dim,), name='z_sampling') -x = Dense(shape[1] * shape[2] * shape[3], - activation='relu')(latent_inputs) -x = Reshape((shape[1], shape[2], shape[3]))(x) -``` - -```py -for i in range(2): - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - activation='relu', - strides=2, - padding='same')(x) - filters //= 2 -``` - -```py -outputs = Conv2DTranspose(filters=1, - kernel_size=kernel_size, - activation='sigmoid', - padding='same', - name='decoder_output')(x) -``` - -```py -# instantiate decoder model -decoder = Model(latent_inputs, outputs, name='decoder') -``` - -```py -# instantiate VAE model -outputs = decoder(encoder(inputs)[2]) -vae = Model(inputs, outputs, name='vae') -``` - -“图 8.1.8”显示了 CNN 编码器模型的两个输出,即潜向量的均值和方差。 lambda 函数实现了重新参数化技巧,将随机潜码的采样推送到 VAE 网络之外: - -![A screenshot of a cell phone Description automatically generated](img/B14853_08_08.png) - -图 8.1.8:VAE CNN 的编码器 - -“图 8.1.9”显示了 CNN 解码器模型。 2 维输入来自 lambda 函数。 输出是重构的 MNIST 数字: - -![A screenshot of a cell phone Description automatically generated](img/B14853_08_09.png) - -图 8.1.9:VAE CNN 的解码器 - -“图 8.1.10”显示完整的 CNN VAE 模型。 通过将编码器和解码器模型结合在一起制成: - -![](img/B14853_08_10.png) - -图 8.1.10:使用 CNN 的 VAE 模型 - -对 VAE 进行了 30 个周期的训练。“图 8.1.11”显示了在导航 VAE 的连续潜在空间时数字的分布。 例如,从中间到右边从 2 变为 0: - -![A picture containing tree Description automatically generated](img/B14853_08_11.png) - -图 8.1.11:MNIST 数字标签作为测试数据集(VAE CNN)的潜在向量平均值的函数。 原始图像可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter8-vae)中找到。 - -“图 8.1.12”向我们展示了生成模型的输出。 从质量上讲,与“图 8.1.7”(具有 MLP 实现)相比,模棱两可的位数更少: - -![](img/B14853_08_12.png) - -图 8.1.12:根据潜在向量平均值(VAE CNN)生成的数字。 为了便于解释,均值的范围类似于图 8.1.11 - -前的两节讨论了使用 MLP 或 CNN 的 VAE 的实现。 我们分析了两种实现方式的结果,结果表明 CNN 可以减少参数数量并提高感知质量。 在下一节中,我们将演示如何在 VAE 中实现条件,以便我们可以控制要生成的数字。 - -# 2\. 条件 VAE(CVAE) - -有条件的 VAE [2]与 CGAN 相似。 在 MNIST 数据集的上下文中,如果随机采样潜在空间,则 VAE 无法控制将生成哪个数字。 CVAE 可以通过包含要产生的数字的条件(单标签)来解决此问题。 该条件同时施加在编码器和解码器输入上。 - -正式地,将“公式 8.1.10”中 VAE 的核心公式修改为包括条件`c`: - -![](img/B14853_08_113.png) (Equation 8.2.1) - -与 VAE 相似,“公式 8.2.1”表示如果要最大化输出条件`c`和`P[θ](x | c)`,则必须最小化两个损失项: - -* 给定潜在向量和条件,解码器的重建损失。 -* 给定潜在向量和条件的编码器之间的 KL 损失以及给定条件的先验分布。 与 VAE 相似,我们通常选择`P[θ](x | c) = P(x | c) = N(0, 1)`。 - -实现 CVAE 需要对 VAE 的代码进行一些修改。 对于 CVAE,使用 VAE CNN 实现是因为它可以形成一个较小的网络,并产生感知上更好的数字。 - -“列表 8.2.1”突出显示了针对 MNIST 数字的 VAE 原始代码所做的更改。 编码器输入现在是原始输入图像及其单标签的连接。 解码器输入现在是潜在空间采样与其应生成的图像的一键热标签的组合。 参数总数为 174,437。 与 β-VAE 相关的代码将在本章下一节中讨论。 - -损失函数没有改变。 但是,在训练,测试和结果绘制过程中会提供单热标签。 - -“列表 8.2.1”:`cvae-cnn-mnist-8.2.1.py` - -`tf.keras`中使用 CNN 层的 CVAE。 重点介绍了为支持 CVAE 而进行的更改: - -```py -# compute the number of labels -num_labels = len(np.unique(y_train)) -``` - -```py -# network parameters -input_shape = (image_size, image_size, 1) -label_shape = (num_labels, ) -batch_size = 128 -kernel_size = 3 -filters = 16 -latent_dim = 2 -epochs = 30 -``` - -```py -# VAE model = encoder + decoder -# build encoder model -inputs = Input(shape=input_shape, name='encoder_input') -y_labels = Input(shape=label_shape, name='class_labels') -x = Dense(image_size * image_size)(y_labels) -x = Reshape((image_size, image_size, 1))(x) -x = keras.layers.concatenate([inputs, x]) -for i in range(2): - filters *= 2 - x = Conv2D(filters=filters, - kernel_size=kernel_size, - activation='relu', - strides=2, - padding='same')(x) -``` - -```py -# shape info needed to build decoder model -shape = K.int_shape(x) -``` - -```py -# generate latent vector Q(z|X) -x = Flatten()(x) -x = Dense(16, activation='relu')(x) -z_mean = Dense(latent_dim, name='z_mean')(x) -z_log_var = Dense(latent_dim, name='z_log_var')(x) -``` - -```py -# use reparameterization trick to push the sampling out as input -# note that "output_shape" isn't necessary -# with the TensorFlow backend -z = Lambda(sampling, - output_shape=(latent_dim,), - name='z')([z_mean, z_log_var]) -``` - -```py -# instantiate encoder model -encoder = Model([inputs, y_labels], - [z_mean, z_log_var, z], - name='encoder') -``` - -```py -# build decoder model -latent_inputs = Input(shape=(latent_dim,), name='z_sampling') -x = concatenate([latent_inputs, y_labels]) -x = Dense(shape[1]*shape[2]*shape[3], activation='relu')(x) -x = Reshape((shape[1], shape[2], shape[3]))(x) -``` - -```py -for i in range(2): - x = Conv2DTranspose(filters=filters, - kernel_size=kernel_size, - activation='relu', - strides=2, - padding='same')(x) - filters //= 2 -``` - -```py -outputs = Conv2DTranspose(filters=1, - kernel_size=kernel_size, - activation='sigmoid', - padding='same', - name='decoder_output')(x) -``` - -```py -# instantiate decoder model -decoder = Model([latent_inputs, y_labels], - outputs, - name='decoder') -# instantiate vae model -outputs = decoder([encoder([inputs, y_labels])[2], y_labels]) -cvae = Model([inputs, y_labels], outputs, name='cvae') -``` - -“图 8.2.1”显示了 CVAE 模型的编码器。 附加输入,即单热向量`class_labels`形式的条件标签表示: - -![A screenshot of a cell phone Description automatically generated](img/B14853_08_13.png) - -图 8.2.1:CVAE CNN 中的编码器。 输入现在包括 VAE 输入和条件标签的连接 - -“图 8.2.2”显示了 CVAE 模型的解码器。 附加输入,即单热向量`class_labels`形式的条件标签表示: - -![A screenshot of a cell phone Description automatically generated](img/B14853_08_14.png) - -图 8.2.2:CVAE CNN 中的解码器。 输入现在包括 z 采样和条件标签的连接 - -“图 8.2.3”显示了完整的 CVAE 模型,该模型是编码器和解码器结合在一起的。 附加输入,即单热向量`class_labels`形式的条件标签: - -![](img/B14853_08_15.png) - -图 8.2.3:使用 CNN 的 CVAE 模型。输入现在包含一个 VAE 输入和一个条件标签 - -在“图 8.2.4”中,每个标记的平均值分布在 30 个周期后显示。 与前面章节中的“图 8.1.6”和“图 8.1.11”不同,每个标签不是集中在一个区域上,而是分布在整个图上。 这是预期的,因为潜在空间中的每个采样都应生成一个特定的数字。 浏览潜在空间会更改该特定数字的属性。 例如,如果指定的数字为 0,则在潜伏空间中导航仍将产生 0,但是诸如倾斜角度,厚度和其他书写样式方面的属性将有所不同。 - -![](img/B14853_08_16.png) - -图 8.2.4:作为测试数据集(CVAE CNN)的潜在向量平均值的函数的 MNIST 数字标签。 原始图像可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter8-vae)中找到。 - -“图 8.2.4”在“图 8.2.5”中更清楚地显示,数字 0 到 5。每个帧都有相同的数字,并且属性在我们浏览时顺畅地变化。 潜在代码: - -![](img/B14853_08_17.png) - -图 8.2.5:根据潜在向量平均值和单热点标签(CVAE CNN)生成的数字 0 至 5。 为了便于解释,均值的范围类似于图 8.2.4。 - -“图 8.2.6”显示“图 8.2.4”,用于数字 6 至 9: - -![](img/B14853_08_18.png) - -图 8.2.6:根据潜在向量平均值和单热点标签(CVAE CNN)生成的数字 6 至 9。 为了便于解释,均值的范围类似于图 8.2.4。 - -为了便于比较,潜向量的值范围与“图 8.2.4”中的相同。 使用预训练的权重,可以通过执行以下命令来生成数字(例如 0): - -```py -python3 cvae-cnn-mnist-8.2.1.py –bce --weights=cvae_cnn_mnist.tf --digit=0 -``` - -在“图 8.2.5”和“图 8.2.6”中,可以注意到,每个数字的宽度和圆度(如果适用)随`z[0]`的变化而变化。 从左到右追踪。 同时,当`z[1]`从上到下导航时,每个数字的倾斜角度和圆度(如果适用)也会发生变化。 随着我们离开分布中心,数字的图像开始退化。 这是可以预期的,因为潜在空间是一个圆形。 - -属性中其他明显的变化可能是数字特定的。 例如,数字 1 的水平笔划(手臂)在左上象限中可见。 数字 7 的水平笔划(纵横线)只能在右象限中看到。 - -在下一节中,我们将发现 CVAE 实际上只是另一种称为 β-VAE 的 VAE 的特例。 - -# 3\. β-VAE – 具有纠缠的潜在表示形式的 VAE - -在“第 6 章”,“非纠缠表示 GAN”中,讨论了潜码非纠缠表示的概念和重要性。 我们可以回想起,一个纠缠的表示是单个潜伏单元对单个生成因子的变化敏感,而相对于其他因子的变化相对不变[3]。 更改潜在代码会导致生成的输出的一个属性发生更改,而其余属性保持不变。 - -在同一章中,InfoGAN [4]向我们展示了对于 MNIST 数据集,可以控制生成哪个数字以及书写样式的倾斜度和粗细。 观察上一节中的结果,可以注意到,VAE 在本质上使潜向量维解开了一定程度。 例如,查看“图 8.2.6”中的数字 8,从上到下导航`z[1]`会减小宽度和圆度,同时顺时针旋转数字。 从左至右增加`z[0]`也会在逆时针旋转数字时减小宽度和圆度。 换句话说,`z[1]`控制顺时针旋转,而`z[0]`影响逆时针旋转,并且两者都改变​​宽度和圆度。 - -在本节中,我们将演示对 VAE 损失函数的简单修改会迫使潜在代码进一步解开纠缠。 修改为正恒重`β > 1`,用作 KL 损失的调节器: - -![](img/B14853_08_121.png) (Equation 8.3.1) - -VAE 的这种变化称为 β-VAE [5]。 `β`的隐含效果是更严格的标准差。 换句话说,`β`强制后验分布中的潜码`Q[φ](z | x)`独立。 - -实现 β-VAE 很简单。 例如,对于上一个示例中的 CVAE,所需的修改是`kl_loss`中的额外`beta`因子: - -```py -kl_loss = 1 + z_log_var - K.square(z_mean) - K.exp(z_log_var) -kl_loss = K.sum(kl_loss, axis=-1) -kl_loss *= -0.5 * beta -``` - -CVAE 是 β-VAE 的特例,其中`β = 1`。 其他一切都一样。 但是,确定的值需要一些反复试验。 为了潜在的代码独立性,在重构误差和正则化之间必须有一个仔细的平衡。 解缠最大在`β = 9`附近。 当中`β = 9`的值时,β-VAE 仅被迫学习一个解纠缠的表示,而忽略另一个潜在维度。 - -“图 8.3.1”和“图 8.3.2”显示 β-VAE 的潜向量平均值,其中`β = 9`和`β = 10`: - -![](img/B14853_08_19.png) - -图 8.3.1:MNIST 数字标签与测试数据集的潜在向量平均值的函数(β-VAE,`β = 9`)。 原始图像可以在该书的 [GitHub 存储库](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter8-vae)中找到。 - -`β = 9`时,与 CVAE 相比,分布具有较小的标准差。 在`β = 10`的情况下,仅学习了潜在代码。 分布实际上缩小为一个维度,编码器和解码器忽略了第一潜码`z[0]`。 - -![](img/B14853_08_20.png) - -图 8.3.2:MNIST 数字标签与测试数据集的潜向量平均值的函数(β-VAE 和`β = 10`) - -[原始图像可以在该书的 GitHub 存储库中找到](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter8-vae)。 - -这些观察结果反映在“图 8.3.3”中。 具有`β = 9`的 β-VAE 具有两个实际上独立的潜在代码。 `z[0]`确定书写样式的倾斜度,而`z[1]`指定数字的宽度和圆度(如果适用)。 对于中`β = 10`的 β-VAE,`z[0]`被静音。 `z[0]`的增加不会显着改变数字。`z[1]`确定书写样式的倾斜角度和宽度: - -![A picture containing grass, window Description automatically generated](img/B14853_08_21.png) - -图 8.3.3:根据潜在向量平均值和单热点标签(β-VAE,`β = 1, 9, 10`)生成的数字 0 至 3。 为了便于解释,均值的范围类似于图 8.3.1。 - -β-VAE 的`tf.keras`代码具有预训练的权重。 要使用`β = 9`生成数字 0 来测试 β-VAE,我们需要运行以下命令: - -```py -python3 cvae-cnn-mnist-8.2.1.py --beta=9 --bce --weights=beta-cvae_cnn_mnist.tf --digit=0 -``` - -总而言之,我们已经证明与 GAN 相比,在 β-VAE 上更容易实现解缠表示学习。 我们所需要做的就是调整单个超参数。 - -# 4\. 总结 - -在本章中,我们介绍了 VAE 的原理。 正如我们从 VAE 原理中学到的那样,从两次尝试从潜在空间创建合成输出的角度来看,它们都与 GAN 相似。 但是,可以注意到,与 GAN 相比,VAE 网络更简单,更容易训练。 越来越清楚的是 CVAE 和 β-VAE 在概念上分别类似于条件 GAN 和解缠表示 GAN。 - -VAE 具有消除潜在向量纠缠的内在机制。 因此,构建 β-VAE 很简单。 但是,我们应该注意,可解释和解开的代码对于构建智能体很重要。 - -在下一章中,我们将专注于强化学习。 在没有任何先验数据的情况下,智能体通过与周围的世界进行交互来学习。 我们将讨论如何为智能体的正确行为提供奖励,并为错误的行为提供惩罚。 - -# 5\. 参考 - -1. `Diederik P. Kingma and Max Welling. Auto-encoding Variational Bayes. arXiv preprint arXiv:1312.6114, 2013 (https://arxiv.org/pdf/1312.6114.pdf).` -1. `Kihyuk Sohn, Honglak Lee, and Xinchen Yan. Learning Structured Output Representation Using Deep Conditional Generative Models. Advances in Neural Information Processing Systems, 2015 (http://papers.nips.cc/paper/5775-learning-structured-output-representation-using-deep-conditional-generative-models.pdf).` -1. `Yoshua Bengio, Aaron Courville, and Pascal Vincent. Representation Learning.` -1. `A Review and New Perspectives. IEEE transactions on Pattern Analysis and Machine Intelligence 35.8, 2013: 1798-1828 (https://arxiv.org/pdf/1206.5538.pdf).` -1. `Xi Chen et al.: Infogan: Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets. Advances in Neural Information Processing Systems, 2016 (http://papers.nips.cc/paper/6399-infogan-interpretable-representation-learning-by-information-maximizing-generative-adversarial-nets.pdf).` -1. `I. Higgins, L. Matthey, A. Pal, C. Burgess, X. Glorot, M. Botvinick, S. Mohamed, and A. Lerchner. -VAE: Learning Basic Visual Concepts with a Constrained Variational Framework. ICLR, 2017 (https://openreview.net/pdf?id=Sy2fzU9gl).` -1. `Carl Doersch. Tutorial on variational autoencoders. arXiv preprint arXiv:1606.05908, 2016 (https://arxiv.org/pdf/1606.05908.pdf).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/09.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/09.md deleted file mode 100644 index 6795b85d..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/09.md +++ /dev/null @@ -1,1187 +0,0 @@ -# 九、深度强化学习 - -**强化学习**(**RL**)是智能体程序用于决策的框架。 智能体不一定是软件实体,例如您在视频游戏中可能看到的那样。 相反,它可以体现在诸如机器人或自动驾驶汽车之类的硬件中。 内在的智能体可能是充分理解和利用 RL 的最佳方法,因为物理实体与现实世界进行交互并接收响应。 - -该智能体位于**环境**中。 环境具有**状态**,可以部分或完全观察到。 该智能体具有一组**操作**,可用于与环境交互。 动作的结果将环境转换为新状态。 执行动作后,会收到相应的标量**奖励**。 - -智能体的目标是通过学习**策略**来最大化累积的未来奖励,该策略将决定在特定状态下应采取的行动。 - -RL 与人类心理学有很强的相似性。 人类通过体验世界来学习。 错误的行为会导致某种形式的惩罚,将来应避免使用,而正确的行为应得到奖励并应予以鼓励。 这种与人类心理学的强相似之处使许多研究人员相信 RL 可以将引向真正的**人工智能**(**AI**)。 - -RL 已经存在了几十年。 但是,除了简单的世界模型之外,RL 还在努力扩展规模。 这是,其中**深度学习**(**DL**)开始发挥作用。 它解决了这个可扩展性问题,从而开启了**深度强化学习**(**DRL**)的时代。 在本章中,我们的重点是 DRL。 DRL 中值得注意的例子之一是 DeepMind 在智能体上的工作,这些智能体能够在不同的视频游戏上超越最佳的人类表现。 - -在本章中,我们将讨论 RL 和 DRL。 - -总之,本章的目的是介绍: - -* RL 的原理 -* RL 技术,Q 学习 -* 高级主题,包括**深度 Q 网络**(**DQN**)和**双重 Q 学习**(**DDQN**) -* 关于如何使用`tf.keras`在 Python 和 DRL 上实现 RL 的说明 - -让我们从 RL 的基本原理开始。 - -# 1\. 强化学习原理(RL) - -“图 9.1.1”显示了用于描述 RL 的感知动作学习循环。 环境是苏打水可以坐在地板上。 智能体是一个移动机器人,其目标是拾取苏打水。 它观察周围的环境,并通过车载摄像头跟踪汽水罐的位置。 观察结果以一种状态的形式进行了汇总,机器人将使用该状态来决定要采取的动作。 所采取的动作可能与低级控制有关,例如每个车轮的旋转角度/速度,手臂的每个关节的旋转角度/速度以及抓手是打开还是关闭。 - -可替代地,动作可以是高级控制动作,诸如向前/向后移动机器人,以特定角度转向以及抓取/释放。 将夹持器从汽水中移开的任何动作都会得到负回报。 缩小抓取器位置和苏打之间的缝隙的任何动作都会获得积极的回报。 当机械臂成功捡起汽水罐时,它会收到丰厚的回报。 RL 的目标是学习最佳策略,该策略可帮助机器人决定在给定状态下采取哪种行动以最大化累积的折扣奖励: - -![](img/B14853_09_01.png) - -图 9.1.1:RL 中的感知-动作-学习循环 - -形式上,RL 问题可以描述为 **Markov 决策过程**(**MDP**)。 - -为简单起见,我们将假定为*确定性*环境,在该环境中,给定状态下的某个动作将始终导致已知的下一个状态和奖励。 在本章的后面部分,我们将研究如何考虑随机性。 在时间步`t`时: - -* 环境处于状态空间`S`的状态下,状态`s[0]`,该状态可以是离散的也可以是连续的。 起始状态为`s[0]`,而终止状态为`s[T]`。 -* 智能体通过遵循策略`π(a[t] | s[t])`从操作空间`A`采取操作,即`s[a]`。 `A`可以是离散的或连续的。 -* 环境使用状态转换动态`T(s[t + 1] | s[t], a[t])`转换为新状态,`s[t + 1]`。 下一个状态仅取决于当前状态和操作。 智能体不知道`T`。 -* 智能体使用奖励函数接收标量奖励,`r[t + 1] = R(s[t], a[t])`,以及`r: A x S -> R`。 奖励仅取决于当前状态和操作。 智能体不知道`R`。 -* 将来的奖励折扣为`γ^k`,其中`γ ∈ [0, 1]`和`k`是未来的时间步长。 -* *地平线*,`H`是完成从`s[0]`到`s[T]`的一集所需的时间步长`T`。 - -该环境可以是完全或部分可观察的。 后者也称为**部分可观察的 MDP** 或 **POMDP**。 在大多数情况下,完全观察环境是不现实的。 为了提高的可观察性,当前的观测值也考虑了过去的观测值。 状态包括对环境的足够观察,以使策略决定采取哪种措施。 回忆“图 9.1.1”,这可能是汽水罐相对于机器人抓手的三维位置,如机器人摄像头所估计的那样。 - -每当环境转换到新状态时,智能体都会收到标量奖励`r[t + 1]`。 在“图 9.1.1”中,每当机器人靠近汽水罐时,奖励可能为 +1;当机器人离汽水罐更远时,奖励为 -1;当机器人关闭夹具并成功捡起苏打时,奖励为 +100。 能够。 智能体的目标是学习一种最佳策略`π*`,该策略可使所有状态的收益最大化: - -![](img/14853_09_012.png) (Equation 9.1.1) - -回报定义为折扣累积奖励`R[t] = Σ γ^t r[t+k], k = 0, ..., T`。 从“公式 9.1.1”可以看出,与通常的`γ^k < 1.0`相比,与立即获得的奖励相比,未来的奖励权重较低。 在极端情况下,当`γ = 0`时,仅立即获得奖励很重要。 当`γ = 1`时,将来的奖励与立即奖励的权重相同。 - -遵循任意策略`π`,可以将回报解释为对给定状态值的度量: - -![](img/14853_09_019.png) (Equation 9.1.2) - -换句话说,RL 问题是智能体的目标,是学习使所有状态`s`最大化的最优策略`V^π`: - -![](img/14853_09_021.png) (Equation 9.1.3) - -最优策略的值函数就是`V*`。 在“图 9.1.1”中,最佳策略是生成最短动作序列的一种,该动作序列使机器人越来越靠近苏打罐,直到被取走为止。 状态越接近目标状态,其值越高。 可以将导致目标(或最终状态)的事件序列建模为策略的*轨迹*或*部署*: - -![](img/14853_09_023.png) (Equation 9.1.4) - -如果 MDP 是偶发的,则当智能体到达终端状态`s[T]`时,状态将重置为`s[0]`。 如果`T`是有限的,则我们的水平范围是有限的。 否则,视野是无限的。 在“图 9.1.1”中,如果 MDP 是情景*剧集*,则在收集苏打罐后,机器人可能会寻找另一个苏打罐来拾取,并且 RL 问题重发。 - -因此,RL 的主要目标是找到一种使每个状态的值最大化的策略。 在下一部分中,我们将介绍可用于最大化值函数的策略学习算法。 - -# 2\. Q 值 - -如果 RL 问题是找到`π*`,则智能体如何通过与环境交互来学习?“公式 9.1.3”并未明确指出尝试进行的操作以及计算收益的后续状态。 在 RL 中,使用 Q 值更容易学习`π*`: - -![](img/14853_09_026.png) (Equation 9.2.1) - -哪里: - -![](img/14853_09_027.png) (Equation 9.2.2) - -换句话说,不是找到使所有状态的值最大化的策略,而是“公式 9.2.1”寻找使所有状态的质量(Q)值最大化的操作。 在找到 Q 值函数之后,分别由“公式 9.2.2”和“公式 9.1.3”确定`V*`,因此确定了`π*`。 - -如果对于每个动作,都可以观察到奖励和下一状态,则可以制定以下迭代或反复试验算法来学习 Q 值: - -![](img/14853_09_030.png) (Equation 9.2.3) - -为了简化符号,`s'`和`a'`分别是下一个状态和动作。 “公式 9.2.3”被称为贝尔曼方程,它是 Q 学习算法的核心。 Q 学习尝试根据当前状态和作用来近似返回值或值的一阶展开(“公式 9.1.2”)。 从对环境动态的零知识中,智能体尝试执行操作`a`,观察以奖励`r`和下一个状态`s'`的形式发生的情况。 `max[a'] Q(s', a')`选择下一个逻辑动作,该动作将为下一个状态提供最大 Q 值。 有了“公式 9.2.3”中的所有项,该当前状态-动作对的 Q 值就会更新。 迭代地执行更新将最终使智能体能够学习 Q 值函数。 - -Q 学习是一种*脱离策略* RL 算法。 它学习了如何通过不直接从策略中抽取经验来改进策略。 换句话说,Q 值的获取与智能体所使用的基础策略无关。 当 Q 值函数收敛时,才使用“公式 9.2.1”确定最佳策略。 - -在为提供有关如何使用 Q 学习的示例之前,请注意,智能体必须在不断利用其到目前为止所学知识的同时不断探索其环境。 这是 RL 中的问题之一-在*探索*和*开发*之间找到适当的平衡。 通常,在学习开始时,动作是随机的(探索)。 随着学习的进行,智能体会利用 Q 值(利用)。 例如,一开始,90% 的动作是随机的,而 10% 的动作则来自 Q 值函数。 在每个剧集的结尾,这逐渐减少。 最终,该动作是 10% 随机的,并且是 Q 值函数的 90%。 - -在下一节中,我们将给出有关在简单的确定性环境中如何使用 Q 学习的具体示例。 - -# 3\. Q 学习实例 - -为了说明 Q 学习算法,我们需要考虑一个简单的确定性环境,如图“图 9.3.1”所示。 环境具有六个状态。 - -显示允许的过渡的奖励。 在两种情况下,奖励是非零的。 转换为**目标**(`G`)状态可获得 +100 的奖励,同时移至**洞**(`H`)状态具有 -100 奖励。 这两个状态是终端状态,从**开始**状态构成一个剧集的结尾: - -![](img/B14853_09_02.png) - -图 9.3.1:简单确定性世界中的奖励 - -为了使每个状态的身份正式化,我们使用`(行, 列)`标识符,如图“图 9.3.2”所示。 由于智能体尚未了解有关其环境的任何信息,因此“图 9.3.2”中所示的 Q 表的初始值为零。 在此示例中,折扣因子`γ = 0.9`。 回想一下,在当前 Q 值的估计中,折扣因子确定了未来 Q 值的权重,该权重是步数`γ^k`的函数。 在“公式 9.2.3”中,我们仅考虑近期 Q 值`k = 1`。 - -![](img/B14853_09_03.png) - -图 9.3.2:简单确定性环境中的状态和智能体的初始 Q 表 - -最初,智能体采用的策略是 90% 的时间选择随机操作,并 10% 的时间使用 Q 表。 假设第一个动作是随机选择的,并且指示向右移动。“图 9.3.3”说明了向右移动时状态`(0, 0)`的新 Q 值的计算。 下一个状态是`(0, 1)`。 奖励为 0,所有下一个状态的 Q 值的最大值为零。 因此,向右移动的状态`(0, 0)`的 Q 值保持为 0。 - -为了轻松跟踪初始状态和下一个状态,我们在环境和 Q 表上使用不同的灰色阴影-初始状态浅灰色,下一个状态灰色。 - -在为下一个状态选择下一个动作时,候选动作位于较粗的边框中: - -![](img/B14853_09_04.png) - -图 9.3.3:假设智能体采取的行动是向右移动,则显示状态`(0, 0)`的 Q 值的更新 - -假设下一个随机选择的动作是向下移动。“图 9.3.4”显示状态`(0, 1)`的 Q 值沿向下方向的移动没有​​变化: - -![](img/B14853_09_05.png) - -图 9.3.4:假设智能体选择的动作是向下移动,则显示状态`(0, 1)`的 Q 值的更新 - -在“图 9.3.5”中,智能体的第三个随机动作是向右移动。 - -![](img/B14853_09_06.png) - -图 9.3.5:假设智能体选择的动作是向右移动,则显示状态`(1, 1)`的 Q 值的更新 - -它遇到了,`H`状态,并获得了 -100 奖励。 这次,更新不为零。 向右移动时,状态`(1, 1)`的新 Q 值为 -100。 注意,由于这是终端状态,因此没有下一个状态。 一集刚刚结束,**智能体**返回到**开始**状态。 - -假设**智能体**仍处于探索模式,如图“图 9.3.6”所示: - -![](img/B14853_09_07.png) - -图 9.3.6:假设智能体选择的动作是向右连续两次移动,则显示状态`(0, 1)`的 Q 值的更新 - -为第二集采取的第一步是向右移动。 正如预期的那样,更新为 0。但是,它选择的第二个随机动作也是向右移动。 智能体到达`G`状态并获得 +100 的巨额奖励。 向右移动的状态`(0, 1)`的 Q 值变为 100。完成第二集,并且**智能体**返回到**启动**状态。 - -在第三集开始时,智能体采取的随机行动是向右移动。 现在,状态`(0, 0)`的 Q 值将更新为非零值,因为下一个状态的可能动作将最大 Q 值设为 100。“图 9.3.7”显示了所涉及的计算。 下一个状态`(0, 1)`的 Q 值波动回到较早的状态`(0, 0)`。 这就像对帮助找到`G`状态的早期状态表示赞赏。 - -![](img/B14853_09_08.png) - -图 9.3.7:假设智能体选择的动作是向右移动,则显示状态`(0, 0)`的 Q 值的更新 - -Q 表的进步很大。 实际上,在下一集中,如果由于某种原因该策略决定使用 Q 表而不是随机探索环境,则第一个动作是根据“图 9.3.8”中的计算向右移动。 在 Q 表的第一行中,导致最大 Q 值的动作是向右移动。 对于下一个状态`(0, 1)`,Q 表的第二行表明下一个动作仍然是向右移动。 **智能体**已成功实现其目标。 该策略指导智能体采取了正确的措施来实现其目标: - -![](img/B14853_09_09.png) - -图 9.3.8:在这种情况下,智能体的策略决定利用 Q 表来确定状态`(0, 0)`和`(0, 1)`的动作。 Q 表建议两个状态都向右移动 - -如果 Q 学习算法继续无限期运行,则 Q 表将收敛。 收敛的假设是 RL 问题必须是具有有限奖励的确定性 MDP,并且所有状态都将被无限次地访问。 - -在下一节中,我们将使用 Python 模拟环境。 我们还将展示 Q 学习算法的代码实现。 - -## 用 Python 进行 Q 学习 - -上一节中讨论的环境和 Q 学习可以在 Python 中实现。 由于该策略只是一个简单的表,因此在此时,无需使用`tf.keras`库。“列表 9.3.1”显示了`q-learning-9.3.1.py`,它是使用`QWorld`类实现的简单确定性世界(环境,智能体,操作和 Q 表算法)的实现。 为简洁起见,未显示处理用户界面的函数。 - -在此示例中,环境动态由`self.transition_table`表示。 在每个动作中,`self.transition_table`确定下一个状态。 执行动作的奖励存储在`self.reward_table`中。 每次通过`step()`函数执行动作时,都要查阅这两个表。 Q 学习算法由`update_q_table()`函数实现。 每当智能体需要决定要采取的操作时,它都会调用`act()`函数。 策略可以使用 Q 表随机抽取或决定。 所选动作是随机的机会百分比存储在`self.epsilon`变量中,该变量由`update_epsilon()`函数使用固定的`epsilon_decay`更新。 - -在执行“列表 9.3.1”中的代码之前,我们需要运行: - -```py -sudo pip3 install termcolor -``` - -安装`termcolor`包。 该包有助于可视化终端上的文本输出。 - -[完整的代码可以在 GitHub 上找到](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)。 - -“列表 9.3.1”:`q-learning-9.3.1.py` - -具有六个状态的简单确定性 MDP: - -```py -from collections import deque -import numpy as np -import argparse -import os -import time -from termcolor import colored -``` - -```py -class QWorld: - def __init__(self): - """Simulated deterministic world made of 6 states. - Q-Learning by Bellman Equation. - """ - # 4 actions - # 0 - Left, 1 - Down, 2 - Right, 3 - Up - self.col = 4 -``` - -```py - # 6 states - self.row = 6 -``` - -```py - # setup the environment - self.q_table = np.zeros([self.row, self.col]) - self.init_transition_table() - self.init_reward_table() -``` - -```py - # discount factor - self.gamma = 0.9 -``` - -```py - # 90% exploration, 10% exploitation - self.epsilon = 0.9 - # exploration decays by this factor every episode - self.epsilon_decay = 0.9 - # in the long run, 10% exploration, 90% exploitation - self.epsilon_min = 0.1 -``` - -```py - # reset the environment - self.reset() - self.is_explore = True -``` - -```py - def reset(self): - """start of episode""" - self.state = 0 - return self.state -``` - -```py - def is_in_win_state(self): - """agent wins when the goal is reached""" - return self.state == 2 -``` - -```py - def init_reward_table(self): - """ - 0 - Left, 1 - Down, 2 - Right, 3 - Up - ---------------- - | 0 | 0 | 100 | - ---------------- - | 0 | 0 | -100 | - ---------------- - """ - self.reward_table = np.zeros([self.row, self.col]) - self.reward_table[1, 2] = 100. - self.reward_table[4, 2] = -100. -``` - -```py - def init_transition_table(self): - """ - 0 - Left, 1 - Down, 2 - Right, 3 - Up - ------------- - | 0 | 1 | 2 | - ------------- - | 3 | 4 | 5 | - ------------- - """ - self.transition_table = np.zeros([self.row, self.col], - dtype=int) - self.transition_table[0, 0] = 0 - self.transition_table[0, 1] = 3 - self.transition_table[0, 2] = 1 - self.transition_table[0, 3] = 0 -``` - -```py - self.transition_table[1, 0] = 0 - self.transition_table[1, 1] = 4 - self.transition_table[1, 2] = 2 - self.transition_table[1, 3] = 1 -``` - -```py - # terminal Goal state - self.transition_table[2, 0] = 2 - self.transition_table[2, 1] = 2 - self.transition_table[2, 2] = 2 - self.transition_table[2, 3] = 2 -``` - -```py - self.transition_table[3, 0] = 3 - self.transition_table[3, 1] = 3 - self.transition_table[3, 2] = 4 - self.transition_table[3, 3] = 0 -``` - -```py - self.transition_table[4, 0] = 3 - self.transition_table[4, 1] = 4 - self.transition_table[4, 2] = 5 - self.transition_table[4, 3] = 1 -``` - -```py - # terminal Hole state - self.transition_table[5, 0] = 5 - self.transition_table[5, 1] = 5 - self.transition_table[5, 2] = 5 - self.transition_table[5, 3] = 5 -``` - -```py - def step(self, action): - """execute the action on the environment - Argument: - action (tensor): An action in Action space - Returns: - next_state (tensor): next env state - reward (float): reward received by the agent - done (Bool): whether the terminal state - is reached - """ - # determine the next_state given state and action - next_state = self.transition_table[self.state, action] - # done is True if next_state is Goal or Hole - done = next_state == 2 or next_state == 5 - # reward given the state and action - reward = self.reward_table[self.state, action] - # the enviroment is now in new state - self.state = next_state - return next_state, reward, done -``` - -```py - def act(self): - """determine the next action - either fr Q Table(exploitation) or - random(exploration) - Return: - action (tensor): action that the agent - must execute - """ - # 0 - Left, 1 - Down, 2 - Right, 3 - Up - # action is from exploration - if np.random.rand() <= self.epsilon: - # explore - do random action - self.is_explore = True - return np.random.choice(4,1)[0] -``` - -```py - # or action is from exploitation - # exploit - choose action with max Q-value - self.is_explore = False - action = np.argmax(self.q_table[self.state]) - return action -``` - -```py - def update_q_table(self, state, action, reward, next_state): - """Q-Learning - update the Q Table using Q(s, a) - Arguments: - state (tensor) : agent state - action (tensor): action executed by the agent - reward (float): reward after executing action - for a given state - next_state (tensor): next state after executing - action for a given state - """ - # Q(s, a) = reward + gamma * max_a' Q(s', a') - q_value = self.gamma * np.amax(self.q_table[next_state]) - q_value += reward - self.q_table[state, action] = q_value -``` - -```py - def update_epsilon(self): - """update Exploration-Exploitation mix""" - if self.epsilon > self.epsilon_min: - self.epsilon *= self.epsilon_decay -``` - -感知动作学习循环在“列表 9.3.2”中进行了说明。 在每个剧集中,环境都会重置为**开始**状态。 选择要执行的动作并将其应用于环境。 观察**奖励**和**下一个**状态,并将其用于更新 Q 表。 达到**目标**或**洞**状态后,剧集完成(`done = True`)。 - -对于此示例,Q 学习运行 100 集或 10 获胜,以先到者为准。 由于在每个剧集中变量的值均降低,因此智能体开始倾向于利用 Q 表来确定在给定状态下要执行的动作。 要查看 Q 学习模拟,我们只需要运行以下命令: - -```py -python3 q-learning-9.3.1.py -``` - -“列表 9.3.2”:`q-learning-9.3.1.py` - -主要的 Q 学习循环: - -```py - # state, action, reward, next state iteration - for episode in range(episode_count): - state = q_world.reset() - done = False - print_episode(episode, delay=delay) - while not done: - action = q_world.act() - next_state, reward, done = q_world.step(action) - q_world.update_q_table(state, action, reward, next_state) - print_status(q_world, done, step, delay=delay) - state = next_state - # if episode is done, perform housekeeping - if done: - if q_world.is_in_win_state(): - wins += 1 - scores.append(step) - if wins > maxwins: - print(scores) - exit(0) - # Exploration-Exploitation is updated every episode - q_world.update_epsilon() - step = 1 - else: - step += 1 -``` - -“图 9.3.9”显示了`maxwins = 2000`(达到`2000 x`目标状态)和`delay = 0`时的屏幕截图。 要仅查看最终的 Q 表,请执行: - -```py -python3 q-learning-9.3.1.py --train -``` - -![A screenshot of a cell phone Description automatically generated](img/B14853_09_10.png) - -图 9.3.9:屏幕快照显示智能体在 2,000 次获胜后的 Q 表 - -Q 表已收敛,并显示了智能体可以在给定状态下采取的逻​​辑操作。 例如,在第一行或状态`(0, 0)`中,该策略建议向右移动。 第二行的状态`(0, 1)`也是如此。 第二个动作达到**目标**状态。 `scores`变量转储显示,随着智能体从策略获取正确的操作,所采取的最少步骤数减少了。 - -从“图 9.3.9”,我们可以从“公式 9.2.2”和`V*(s) = max[a] Q(s, a)`计算每个状态的值。 例如,对于状态`(0, 0)`,`V*(s) = max[a](0.0, 72.9, 90.0, 81.0) = 9.0`。 - -“图 9.3.10”显示每种状态的值。 - -![](img/B14853_09_11.png) - -图 9.3.10:图 9.3.9 和公式 9.2.2 中每个状态的值 - -这个简单的示例说明了在简单确定性世界中智能体的 Q 学习的所有元素。 在下一节中,我们将介绍考虑随机性所需的轻微修改。 - -# 4\. 非确定性环境 - -如果环境不确定,则奖励和行动都是概率性的。 新系统是随机的 MDP。 为了反映不确定性报酬,新的值函数为: - -![](img/14853_09_042.png) (Equation 9.4.1) - -贝尔曼方程修改为: - -![](img/14853_09_043.png) (Equation 9.4.2) - -但是,在本章中,我们将重点介绍确定性环境。 在下一节中,我们将提出一种更通用的 Q 学习算法,称为**时差**(**TD**)学习。 - -# 5\. 时差学习 - -Q 学习是更广义的 TD 学习`TD(λ)`的特例。 更具体地说,这是单步 TD 学习的特殊情况,`TD(0)`: - -![](img/14853_09_045.png) (Equation 9.5.1) - -其中`α`是学习率。 注意,当`α = 1`,“公式 9.5.1”与贝尔曼等式相似。 为简单起见,我们还将“公式 9.5.1”称为 Q 学习或广义 Q 学习。 - -以前,我们将 Q 学习称为一种非策略性 RL 算法,因为它学习 Q 值函数而没有直接使用它尝试优化的策略。 *上策略*一步式 TD 学习算法的示例是 SARSA,类似于“公式 9.5.1”: - -![](img/14853_09_048.png) (Equation 9.5.2) - -主要区别是使用已优化的策略来确定`a'`。 必须知道项`s`,`a`,`r`,`s'`和`a'`(因此名称为 SARSA)才能在每次迭代时更新 Q 值函数。 Q 学习和 SARSA 都在 Q 值迭代中使用现有的估计,该过程称为*自举*。 在引导过程中,我们从奖励中更新当前的 Q 值估计,并随后更新 Q 值估计。 - -在提出另一个示例之前,似乎需要合适的 RL 模拟环境。 否则,我们只能对非常简单的问题(如上一个示例)运行 RL 模拟。 幸运的是,OpenAI 创建了 [Gym](https://gym.openai.com),我们将在下一节中介绍。 - -## 在 OpenAI Gym 上进行 Q 学习 - -OpenAI Gym 是的工具包,用于开发和比较 RL 算法。 它适用于大多数 DL 库,包括`tf.keras`。 可以通过运行以下命令来安装健身房: - -```py -sudo pip3 install gym -``` - -该体育馆有多种可以测试 RL 算法的环境,例如玩具文字,经典控件,算法,Atari 和二维/三维机器人。 例如,`FrozenLake-v0`(“图 9.5.1”)是一个玩具文本环境,类似于在 Python Q 学习示例中使用的简单确定性世界: - -![](img/B14853_09_12.png) - -图 9.5.1:OpenAI Gym 中的 FrozenLake-v0 环境 - -`FrozenLake-v0`具有 12 个状态,标记为`S`的状态为起始状态,`F`的状态为湖泊的冰冻部分,这是安全的,`H`为安全状态。 应当避免的空穴状态,`G`是飞盘所在的目标状态。 转换为目标状态的奖励为 +1。 对于所有其他状态,奖励为**零**。 - -在`FrozenLake-v0`中,还有四个可用动作(左,下,右,上),称为动作空间。 但是,与之前的简单确定性世界不同,实际运动方向仅部分取决于所选的动作。 `FrozenLake-v0`环境有两种变体。 滑和不滑。 不出所料,滑动模式更具挑战性。 - -应用于`FrozenLake-v0`的操作将返回观察结果(等效于下一个状态),奖励,完成(无论剧集是否完成)以及调试信息字典。 返回的观察对象捕获环境的可观察属性,称为观察空间。 - -通用 Q 学习可以应用于`FrozenLake-v0`环境。“表 9.5.1”显示了湿滑和非湿滑环境的表现改进。 衡量策略表现的一种方法是执行的事件达到目标状态的百分比。 百分比越高,效果越好。 从大约 1.5% 的纯探查(随机操作)的基准来看,该策略可以在非光滑环境中达到约 76% 的目标状态,在光滑环境中可以达到约 71% 的目标状态。 不出所料,很难控制湿滑的环境。 - -| **模式** | **运行** | **大约百分比的目标** | -| --- | --- | --- | -| 训练非滑动 | `python3 q-frozenlake-9.5.1.py` | 26 | -| 测试非滑动 | `python3 q-frozenlake-9.5.1.py -d` | 76 | -| 纯随机动作非滑动 | `python3 q-frozenlake-9.5.1.py -e` | 1.5 | -| 训练滑动 | `python3 q-frozenlake-9.5.1.py -s` | 26 | -| 测试滑动 | `python3 q-frozenlake-9.5.1.py -s -d` | 71 | -| 纯随机动作滑动 | `python3 q-frozenlake-9.5.1.py -s -e` | 1.5 | - -表 9.5.1:在 FrozenLake-v0 环境中学习率为 0.5 的广义 Q 学习的基线和表现 - -由于该代码仅需要一个 Q 表,因此仍可以在 Python 和 NumPy 中实现。“列表 9.5.1”显示了`QAgent`类的实现。 除了使用 OpenAI Gym 的`FrozenLake-v0`环境之外,最重要的更改是广义 Q 学习的实现,这由`update_q_table()`函数中的“公式 9.5.1”定义。 - -“列表 9.5.1”:`q-frozenlake-9.5.1.py` - -关于 FrozenLake-v0 环境的 Q 学习: - -```py -from collections import deque -import numpy as np -import argparse -import os -import time -import gym -from gym import wrappers, logger -``` - -```py -class QAgent: - def __init__(self, - observation_space, - action_space, - demo=False, - slippery=False, - episodes=40000): - """Q-Learning agent on FrozenLake-v0 environment -``` - -```py - Arguments: - observation_space (tensor): state space - action_space (tensor): action space - demo (Bool): whether for demo or training - slippery (Bool): 2 versions of FLv0 env - episodes (int): number of episodes to train - """ -``` - -```py - self.action_space = action_space - # number of columns is equal to number of actions - col = action_space.n - # number of rows is equal to number of states - row = observation_space.n - # build Q Table with row x col dims - self.q_table = np.zeros([row, col]) -``` - -```py - # discount factor - self.gamma = 0.9 -``` - -```py - # initially 90% exploration, 10% exploitation - self.epsilon = 0.9 - # iteratively applying decay til - # 10% exploration/90% exploitation - self.epsilon_min = 0.1 - self.epsilon_decay = self.epsilon_min / self.epsilon - self.epsilon_decay = self.epsilon_decay ** \ - (1\. / float(episodes)) -``` - -```py - # learning rate of Q-Learning - self.learning_rate = 0.1 -``` - -```py - # file where Q Table is saved on/restored fr - if slippery: - self.filename = 'q-frozenlake-slippery.npy' - else: - self.filename = 'q-frozenlake.npy' -``` - -```py - # demo or train mode - self.demo = demo - # if demo mode, no exploration - if demo: - self.epsilon = 0 -``` - -```py - def act(self, state, is_explore=False): - """determine the next action - if random, choose from random action space - else use the Q Table - Arguments: - state (tensor): agent's current state - is_explore (Bool): exploration mode or not - Return: - action (tensor): action that the agent - must execute - """ - # 0 - left, 1 - Down, 2 - Right, 3 - Up - if is_explore or np.random.rand() < self.epsilon: - # explore - do random action - return self.action_space.sample() -``` - -```py - # exploit - choose action with max Q-value - action = np.argmax(self.q_table[state]) - return action -``` - -```py - def update_q_table(self, state, action, reward, next_state): - """TD(0) learning (generalized Q-Learning) with learning rate - Arguments: - state (tensor): environment state - action (tensor): action executed by the agent for - the given state - reward (float): reward received by the agent for - executing the action - next_state (tensor): the environment next state - """ - # Q(s, a) += - # alpha * (reward + gamma * max_a' Q(s', a') - Q(s, a)) - q_value = self.gamma * np.amax(self.q_table[next_state]) - q_value += reward - q_value -= self.q_table[state, action] - q_value *= self.learning_rate - q_value += self.q_table[state, action] - self.q_table[state, action] = q_value -``` - -```py - def update_epsilon(self): - """adjust epsilon""" - if self.epsilon > self.epsilon_min: - self.epsilon *= self.epsilon_decay -``` - -“列表 9.5.2”演示了智能体的感知行为学习循环。 在每个剧集中,通过调用`env.reset()`重置环境。 要执行的动作由`agent.act()`选择,并由`env.step(action)`应用于环境。 奖励和下一个状态将被观察并用于更新 Q 表。 - -在每个动作之后,通过`agent.update_q_table()`执行 TD 学习。 由于每次调用`agent.update_epsilon()`时处`self.epsilon`变量的值都会减少,该智能体开始支持利用 Q 表来确定在给定状态下执行的操作。 达到目标或空洞状态后,剧集完成(`done = True`)。 对于此示例,TD 学习运行 4,000 集。 - -“列表 9.5.2”:`q-frozenlake-9.5.1.py`。 - -`FrozenLake-v0`环境的 Q 学习循环: - -```py - # loop for the specified number of episode - for episode in range(episodes): - state = env.reset() - done = False - while not done: - # determine the agent's action given state - action = agent.act(state, is_explore=args.explore) - # get observable data - next_state, reward, done, _ = env.step(action) - # clear the screen before rendering the environment - os.system('clear') - # render the environment for human debugging - env.render() - # training of Q Table - if done: - # update exploration-exploitation ratio - # reward > 0 only when Goal is reached - # otherwise, it is a Hole - if reward > 0: - wins += 1 -``` - -```py - if not args.demo: - agent.update_q_table(state, - action, - reward, - next_state) - agent.update_epsilon() -``` - -```py - state = next_state - percent_wins = 100.0 * wins / (episode + 1) -``` - -`agent`对象可以在湿滑或非湿滑模式下运行。 训练后,智能体可以利用 Q 表选择给定任何策略执行的操作,如“表 9.5.1”的测试模式所示。 如“表 9.5.1”所示,使用学习的策略可显着提高性能。 随着体育馆的使用,不再需要中构建环境的许多代码行。 例如,与上一个示例不同,使用 OpenAI Gym,我们不需要创建状态转换表和奖励表。 - -这将帮助我们专注于构建有效的 RL 算法。 要以慢动作方式运行代码或每个动作延迟 1 秒,请执行以下操作: - -```py -python3 q-frozenlake-9.5.1.py -d -t=1 -``` - -在本节中,我们在更具挑战性的环境中演示了 Q 学习。 我们还介绍了 OpenAI 体育馆。 但是,我们的环境仍然是玩具环境。 如果我们有大量的状态或动作怎么办? 在这种情况下,使用 Q 表不再可行。 在下一节中,我们将使用深度神经网络来学习 Q 表。 - -# 6\. 深度 Q 网络(DQN) - -在小型离散环境中,使用 Q 表执行 Q 学习是很好的选择。 但是,在大多数情况下,当环境具有许多状态或连续时,Q 表是不可行或不实际的。 例如,如果我们观察由四个连续变量组成的状态,则表的大小是无限的。 即使我们尝试将这四个变量离散化为 1,000 个值,表中的总行数也达到了惊人的`1000^4 = 1e12`。 即使经过训练,该表仍是稀疏的–该表中的大多数单元都是零。 - -这个问题的解决方案称为 DQN [2],它使用深度神经网络来近似 Q 表,如图“图 9.6.1”所示。 有两种构建 Q 网络的方法: - -* 输入是状态-动作对,预测是 Q 值 -* 输入是状态,预测是每个动作的 Q 值 - -第一种选择不是最佳的,因为网络被调用的次数等于操作数。 第二种是首选方法。 Q 网络仅被调用一次。 - -最希望得到的作用就是 Q 值最大的作用。 - -![](img/B14853_09_13.png) - -图 9.6.1:深度 Q 网络 - -训练 Q 网络所需的数据来自智能体的经验:`(s[0]a[0]r[1]s[1], s[1]a[1]r[2]s[2],d ..., s[T-1]a[T-1]r[T]s[T])`。 每个训练样本都是经验单元`s[t]a[t]r[t+1]s[t+1]`。 在时间步`t`,`s = s[t]`的给定状态下,使用类似于前一部分的 Q 学习算法来确定动作`a = a[t]`: - -![](img/14853_09_060.png) (Equation 9.6.1) - -为了简化符号,我们省略了下标和粗体字母的使用。 注意,`Q(s, a)`是 Q 网络。 严格来说,它是`Q(a | s)`,因为动作已移至预测阶段(换句话说,是输出),如“图 9.6.1”的右侧所示。 Q 值最高的动作是应用于环境以获得奖励`r = r[t+1]`,下一状态`s' = s[t+1]`和布尔值`done`的动作,指示下一个状态是否为终端 。 根据关于广义 Q 学习的“公式 9.5.1”,可以通过应用所选的操作来确定 MSE 损失函数: - -![](img/14853_09_065.png) (Equation 9.6.2) - -在前面有关 Q 学习和`Q(a | s) -> Q(s, a)`的讨论中,所有项都很熟悉。 项`max[a'] Q(a' | s') -> max[a'] Q(s', a')`。 换句话说,使用 Q 网络,在给定下一个状态的情况下预测每个动作的 Q 值,并从其中获得最大值。 注意,在终端状态下,`s'`,`max[a'] Q(a' | s') -> max[a'] Q(s', a') = 0`。 - -但是,事实证明训练 Q 网络是不稳定的。 导致不稳定的问题有两个:1)样本之间的相关性高; 2)非平稳目标。 高度相关性是由于采样经验的顺序性质。 DQN 通过创建经验缓冲解决了问题。 训练数据是从该缓冲区中随机采样的。 此过程称为**经验回放**。 - -非固定目标的问题是由于目标网络`Q(s', a')`在每小批训练后都会被修改。 目标网络的微小变化会导致策略,数据分布以及当前 Q 值和目标 Q 值之间的相关性发生重大变化。 这可以通过冻结`C`训练步骤的目标网络的权重来解决。 换句话说,创建了两个相同的 Q 网络。 在每个`C`训练步骤中,从训练中的 Q 网络复制目标 Q 网络参数。 - -“算法 9.6.1”中概述了深度 Q 网络算法。 - -“算法 9.6.1”: **DQN 算法** - -要求:将重播内存`D`初始化为容量`N` - -要求:使用随机权重`θ`初始化动作值函数`Q` - -要求:使用权重`θ- = 0`初始化目标操作值函数`Q_target` - -需要:探索率`ε`和折扣系数`γ` - -1. 对于`episode = 1, ..., M`,执行: -2. 给定初始状态`s` -3. 对于`step = 1, ..., T`,执行: -4. 选择动作 - - ![](img/14853_09_082.png) -5. 执行动作`a`,观察奖励`r`,以及下一个状态`s'` -6. 将转换`(s, a, r, s')`存储在`D`中 -7. 更新状态`s = s'` -8. 经验回放 -9. 从`D`中抽样一小部分经验`(s[j], a[j], r[j+1], s[j+1])` -10. ![](img/14853_09_090.png) -11. 在`(Q_max - Q(s[j], a[j]; θ))²`上相对于参数`θ`执行梯度下降步骤。 -12. 定期更新目标网络 -13. 每`C`个步骤,即`Q_target = Q`,换句话说,设置`θ- = θ` -14. `end` - -1. `end` - -“算法 9.6.1”总结了在具有离散动作空间和连续状态空间的环境上实现 Q 学习所需的所有技术。 在下一节中,我们将演示如何在更具挑战性的 OpenAI Gym 环境中使用 DQN。 - -## Keras 中的 DQN - -为了说明 DQN,使用了 OpenAI Gym 的`CartPole-v0`环境。 `CartPole-v0`是极点平衡问题。 目的是防止电杆跌落。 环境是二维的。 动作空间由两个离散的动作(左右移动)组成。 但是,状态空间是连续的,并且包含四个变量: - -* 直线位置 -* 线速度 -* 旋转角度 -* 角速度 - -`CartPole-v0`环境如图 9.6.1 所示: - -![](img/B14853_09_14.png) - -图 9.6.1:CartPole-v0 环境 - -最初,杆是直立的。 杆保持直立的每个时间步长都提供 +1 的奖励。 当极点与垂直方向的夹角超过 15 度或与中心的距离超过 2.4 单位时,剧集结束。 如果在 100 个连续试验中平均奖励为 195.0,则认为`CartPole-v0`问题已解决: - -“列表 9.6.1”向我们展示了`CartPole-v0`的 DQN 实现。 `DQNAgent`类表示使用 DQN 的智能体。 创建了两个 Q 网络: - -* “算法 9.6.1”中的 Q 网络或 Q -* “算法 9.6.1”中的目标 Q 网络或`Q_target` - -两个网络都是 MLP,每个都有 256 个单元的 3 个隐藏层。 这两个网络都是通过`build_model()`方法创建的。 在**经验回放**,`replay()`期间训练 Q 网络。 以`update_weights()`的固定间隔`C = 10`个训练步骤,将 Q 网络参数复制到目标 Q 网络。 在“算法 9.6.1”中,这实现了第 13 行,`Q_target = Q`。 每次发作后,`update_epsilon()`都会降低探索利用的比例,以利用已学习的策略。 - -“列表 9.6.1”:`dqn-cartpole-9.6.1.py` - -`tf.keras`中的 DQN: - -```py -class DQNAgent: - def __init__(self, - state_space, - action_space, - episodes=500): - """DQN Agent on CartPole-v0 environment -``` - -```py - Arguments: - state_space (tensor): state space - action_space (tensor): action space - episodes (int): number of episodes to train - """ - self.action_space = action_space -``` - -```py - # experience buffer - self.memory = [] -``` - -```py - # discount rate - self.gamma = 0.9 -``` - -```py - # initially 90% exploration, 10% exploitation - self.epsilon = 1.0 - # iteratively applying decay til - # 10% exploration/90% exploitation - self.epsilon_min = 0.1 - self.epsilon_decay = self.epsilon_min / self.epsilon - self.epsilon_decay = self.epsilon_decay ** \ - (1\. / float(episodes)) -``` - -```py - # Q Network weights filename - self.weights_file = 'dqn_cartpole.h5' - # Q Network for training - n_inputs = state_space.shape[0] - n_outputs = action_space.n - self.q_model = self.build_model(n_inputs, n_outputs) - self.q_model.compile(loss='mse', optimizer=Adam()) - # target Q Network - self.target_q_model = self.build_model(n_inputs, n_outputs) - # copy Q Network params to target Q Network - self.update_weights() -``` - -```py - self.replay_counter = 0 - self.ddqn = True if args.ddqn else False -``` - -```py - def build_model(self, n_inputs, n_outputs): - """Q Network is 256-256-256 MLP -``` - -```py - Arguments: - n_inputs (int): input dim - n_outputs (int): output dim -``` - -```py - Return: - q_model (Model): DQN - """ - inputs = Input(shape=(n_inputs, ), name='state') - x = Dense(256, activation='relu')(inputs) - x = Dense(256, activation='relu')(x) - x = Dense(256, activation='relu')(x) - x = Dense(n_outputs, - activation='linear', - name='action')(x) - q_model = Model(inputs, x) - q_model.summary() - return q_model -``` - -```py - def act(self, state): - """eps-greedy policy - Return: - action (tensor): action to execute - """ - if np.random.rand() < self.epsilon: - # explore - do random action - return self.action_space.sample() -``` - -```py - # exploit - q_values = self.q_model.predict(state) - # select the action with max Q-value - action = np.argmax(q_values[0]) - return action -``` - -```py - def remember(self, state, action, reward, next_state, done): - """store experiences in the replay buffer - Arguments: - state (tensor): env state - action (tensor): agent action - reward (float): reward received after executing - action on state - next_state (tensor): next state - """ - item = (state, action, reward, next_state, done) - self.memory.append(item) -``` - -```py - def get_target_q_value(self, next_state, reward): - """compute Q_max - Use of target Q Network solves the - non-stationarity problem - Arguments: - reward (float): reward received after executing - action on state - next_state (tensor): next state - Return: - q_value (float): max Q-value computed by - DQN or DDQN - """ - # max Q value among next state's actions - if self.ddqn: - # DDQN - # current Q Network selects the action - # a'_max = argmax_a' Q(s', a') - action = np.argmax(self.q_model.predict(next_state)[0]) - # target Q Network evaluates the action - # Q_max = Q_target(s', a'_max) - q_value = self.target_q_model.predict(\ - next_state)[0][action] - else: - # DQN chooses the max Q value among next actions - # selection and evaluation of action is - # on the target Q Network - # Q_max = max_a' Q_target(s', a') - q_value = np.amax(\ - self.target_q_model.predict(next_state)[0]) -``` - -```py - # Q_max = reward + gamma * Q_max - q_value *= self.gamma - q_value += reward - return q_value -``` - -```py - def replay(self, batch_size): - """experience replay addresses the correlation issue - between samples - Arguments: - batch_size (int): replay buffer batch - sample size - """ - # sars = state, action, reward, state' (next_state) - sars_batch = random.sample(self.memory, batch_size) - state_batch, q_values_batch = [], [] -``` - -```py - # fixme: for speedup, this could be done on the tensor level - # but easier to understand using a loop - for state, action, reward, next_state, done in sars_batch: - # policy prediction for a given state - q_values = self.q_model.predict(state) -``` - -```py - # get Q_max - q_value = self.get_target_q_value(next_state, reward) -``` - -```py - # correction on the Q value for the action used - q_values[0][action] = reward if done else q_value -``` - -```py - # collect batch state-q_value mapping - state_batch.append(state[0]) - q_values_batch.append(q_values[0]) -``` - -```py - # train the Q-network - self.q_model.fit(np.array(state_batch), - np.array(q_values_batch), - batch_size=batch_size, - epochs=1, - verbose=0) -``` - -```py - # update exploration-exploitation probability - self.update_epsilon() -``` - -```py - # copy new params on old target after - # every 10 training updates - if self.replay_counter % 10 == 0: - self.update_weights() -``` - -```py - self.replay_counter += 1 -``` - -```py - def update_epsilon(self): - """decrease the exploration, increase exploitation""" - if self.epsilon > self.epsilon_min: - self.epsilon *= self.epsilon_decay -``` - -为了在“算法 9.6.1”**经验回放**`replay()`中实现第 10 行,对于每个体验单元(`s[j]`,`a[j]`,`r[j + 1]`和`s[j + 1]`)将动作`a[j]`的 Q 值设置为`Q_max`。 所有其他动作的 Q 值保持不变。 - -这是通过 DQNAgent `replay()`函数中的以下行实现的: - -```py -# policy prediction for a given state q_values = self.q_model.predict(state) -# get Q_max -q_value = self.get_target_q_value(next_state) -# correction on the Q value for the action used q_values[0][action] = reward if done else q_value -``` - -如“算法 9.6.1”的第 11 行所示,只有动作`a[j]`具有等于`(Q_max - Q(s[j], a[j]; θ))²`的非零损失。 请注意,假设缓冲区中有足够的数据,换句话说,在每个剧集结束后,“列表 9.6.2”中的感知动作学习循环会调用经验回放。 缓冲区的大小大于或等于批量大小)。 在经验回放期间,会随机采样一批体验单元,并将其用于训练 Q 网络。 - -与 Q 表类似,`act()`实现了 ε-贪婪策略,“公式 9.6.1”。 - -体验由`remember()`存储在重播缓冲区中。 Q 通过`get_target_q_value()`函数计算。 - -“列表 9.6.2”总结了智能体的感知-行动-学习循环。 在每个剧集中,通过调用`env.reset()`重置环境。 要执行的动作由`agent.act()`选择,并由`env.step(action)`应用于环境。 奖励和下一状态将被观察并存储在重播缓冲区中。 在执行每个操作之后,智能体会调用`replay()`来训练 DQN 并调整探索利用比率。 - -当极点与垂直方向的夹角超过 15 度或与中心的距离超过 2.4 单位时,剧集完成(`done = True`)。 对于此示例,如果 DQN 智能体无法解决问题,则 Q 学习最多运行 3,000 集。 如果`average mean_score`奖励在 100 次连续试验`win_trials`中为 195.0,则认为`CartPole-v0`问题已解决。 - -“列表 9.6.2”:`dqn-cartpole-9.6.1.py` - -`tf.keras`中的 DQN 训练循环: - -```py - # Q-Learning sampling and fitting - for episode in range(episode_count): - state = env.reset() - state = np.reshape(state, [1, state_size]) - done = False - total_reward = 0 - while not done: - # in CartPole-v0, action=0 is left and action=1 is right - action = agent.act(state) - next_state, reward, done, _ = env.step(action) - # in CartPole-v0: - # state = [pos, vel, theta, angular speed] - next_state = np.reshape(next_state, [1, state_size]) - # store every experience unit in replay buffer - agent.remember(state, action, reward, next_state, done) - state = next_state - total_reward += reward -``` - -```py - # call experience relay - if len(agent.memory) >= batch_size: - agent.replay(batch_size) -``` - -```py - scores.append(total_reward) - mean_score = np.mean(scores) - if mean_score >= win_reward[args.env_id] \ - and episode >= win_trials: - print("Solved in episode %d: \ - Mean survival = %0.2lf in %d episodes" - % (episode, mean_score, win_trials)) - print("Epsilon: ", agent.epsilon) - agent.save_weights() - break - if (episode + 1) % win_trials == 0: - print("Episode %d: Mean survival = \ - %0.2lf in %d episodes" % - ((episode + 1), mean_score, win_trials)) -``` - -在平均 10 次运行的中,DQN 在 822 集内解决了。 我们需要注意的是,每次训练运行的结果可能会有所不同。 - -自从引入 DQN 以来,连续的论文都提出了对“算法 9.6.1”的改进。 一个很好的例子是**双 DQN(DDQN)**,下面将对其进行讨论。 - -## 双重 Q 学习(DDQN) - -在 DQN 中,目标 Q 网络选择并评估每个动作,从而导致 Q 值过高。 为了解决这个问题,DDQN [3]建议使用 Q 网络选择动作,并使用目标 Q 网络评估动作。 - -在 DQN 中,如“算法 9.6.1”所概述,第 10 行中 Q 值的估计为: - -![](img/14853_09_097.png) - -* `Q_target`选择并评估动作,`a[j+1]`。 - -DDQN 建议将第 10 行更改为: - -![](img/14853_09_100.png) - -项`argmax[a[j+1]] Q(s[j+1], a[j+1]; θ)`使 Q 函数可以选择动作。 然后,该动作由`Q_target`评估。 - -“列表 9.6.3”显示了当我们创建一个新的`DDQNAgent`类时,该类继承自`DQNAgent`类。 只有`get_target_q_value()`方法被覆盖,以实现最大 Q 值计算中的更改。 - -“列表 9.6.3”:`dqn-cartpole-9.6.1.py`: - -```py -class DDQNAgent(DQNAgent): - def __init__(self, - state_space, - action_space, - episodes=500): - super().__init__(state_space, - action_space, - episodes) - """DDQN Agent on CartPole-v0 environment -``` - -```py - Arguments: - state_space (tensor): state space - action_space (tensor): action space - episodes (int): number of episodes to train - """ -``` - -```py - # Q Network weights filename - self.weights_file = 'ddqn_cartpole.h5' -``` - -```py - def get_target_q_value(self, next_state, reward): - """compute Q_max - Use of target Q Network solves the - non-stationarity problem - Arguments: - reward (float): reward received after executing - action on state - next_state (tensor): next state - Returns: - q_value (float): max Q-value computed - """ - # max Q value among next state's actions - # DDQN - # current Q Network selects the action - # a'_max = argmax_a' Q(s', a') - action = np.argmax(self.q_model.predict(next_state)[0]) - # target Q Network evaluates the action - # Q_max = Q_target(s', a'_max) - q_value = self.target_q_model.predict(\ - next_state)[0][action] -``` - -```py - # Q_max = reward + gamma * Q_max - q_value *= self.gamma - q_value += reward - return q_value -``` - -为了进行比较,在平均 10 次运行中,`CartPole-v0`由 DDQN 在 971 个剧集中求解。 要使用 DDQN,请运行以下命令: - -```py -python3 dqn-cartpole-9.6.1.py -d -``` - -DQN 和 DDQN 均表明,借助 DL,Q 学习能够扩展并解决具有连续状态空间和离散动作空间的问题。 在本章中,我们仅在具有连续状态空间和离散动作空间的最简单问题之一上演示了 DQN。 在原始论文中,DQN [2]证明了它可以在许多 Atari 游戏中达到超人的表现水平。 - -# 7\. 总结 - -在本章中,我们已经介绍了 DRL,DRL 是一种强大的技术,许多研究人员认为它是 AI 的最有希望的领先者。 我们已经超越了 RL 的原则。 RL 能够解决许多玩具问题,但是 Q 表无法扩展到更复杂的现实问题。 解决方案是使用深度神经网络学习 Q 表。 但是,由于样本相关性和目标 Q 网络的非平稳性,在 RL 上训练深度神经网络非常不稳定。 - -DQN 提出了一种使用经验回放并将目标网络与受训 Q 网络分离的解决方案。 DDQN 建议通过将动作选择与动作评估分开来最大程度地降低 Q 值,从而进一步改进算法。 DQN 还提出了其他改进建议。 优先经验回放[6]认为,不应对体验缓冲区进行统一采样。 - -取而代之的是,应更频繁地采样基于 TD 误差的更重要的经验,以完成更有效的训练。 文献[7]提出了一种对决网络架构来估计状态值函数和优势函数。 这两个函数均用于估计 Q 值,以加快学习速度。 - -本章介绍的方法是值迭代/拟合。 通过找到最佳值函数间接学习策略。 在下一章中,方法将是使用称为策略梯度方法的一系列算法直接学习最佳策略。 学习策略有很多好处。 特别地,策略梯度方法可以处理离散和连续的动作空间。 - -# 8\. 参考 - -1. `Sutton and Barto: Reinforcement Learning: An Introduction, 2017 (http://incompleteideas.net/book/bookdraft2017nov5.pdf).` -1. `Volodymyr Mnih et al.: Human-level Control through Deep Reinforcement Learning. Nature 518.7540, 2015: 529 (http://www.davidqiu.com:8888/research/nature14236.pdf).` -1. `Hado Van Hasselt, Arthur Guez, and David Silver: Deep Reinforcement Learning with Double Q-Learning. AAAI. Vol. 16, 2016 (http://www.aaai.org/ocs/index.php/AAAI/AAAI16/paper/download/12389/11847).` -1. `Kai Arulkumaran et al.: A Brief Survey of Deep Reinforcement Learning. arXiv preprint arXiv:1708.05866, 2017 (https://arxiv.org/pdf/1708.05866.pdf).` -1. `David Silver: Lecture Notes on Reinforcement Learning (http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching.html).` -1. `Tom Schaul et al.: Prioritized experience replay. arXiv preprint arXiv:1511.05952, 2015 (https://arxiv.org/pdf/1511.05952.pdf).` -1. `Ziyu Wang et al.: Dueling Network Architectures for Deep Reinforcement Learning. arXiv preprint arXiv:1511.06581, 2015 (https://arxiv.org/pdf/1511.06581.pdf).` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/10.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/10.md deleted file mode 100644 index a25c3417..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/10.md +++ /dev/null @@ -1,1189 +0,0 @@ -# 十、策略梯度方法 - -在本章中,我们将介绍在强化学习中直接优化策略网络的算法。 这些算法统称为“策略梯度方法”。 由于策略网络是在训练期间直接优化的,因此策略梯度方法属于*基于策略*强化学习算法的族。 就像我们在“第 9 章”,“深度强化学习”中讨论的基于值的方法一样,策略梯度方法也可以实现为深度强化学习算法。 - -研究策略梯度方法的基本动机是解决 Q 学习的局限性。 我们会回想起 Q 学习是关于选择使状态值最大化的动作。 借助 Q 函数,我们能够确定策略,使智能体能够决定对给定状态采取何种操作。 选择的动作只是使智能体最大化的动作。 在这方面,Q 学习仅限于有限数量的离散动作。 它不能处理连续的动作空间环境。 此外,Q 学习不是直接优化策略。 最后,强化学习是要找到智能体能够使用的最佳策略,以便决定应采取何种行动以最大化回报。 - -相反,策略梯度方法适用于具有离散或连续动作空间的环境。 另外,我们将在本章中介绍的四种策略梯度方法是直接优化策略网络的表现度量。 这样就形成了一个经过训练的策略网络,智能体可以使用该网络来最佳地在其环境中采取行动。 - -总之,本章的目的是介绍: - -* 策略梯度定理 -* 四种策略梯度方法: **REINFORCE** , **带基线的 REINFORCE**, **演员评论家**和**优势演员评论家(A2C)** -* 在连续动作空间环境中如何在`tf.keras`中实现策略梯度方法的指南 - -让我们从定理开始。 - -# 1\. 策略梯度定理 - -如“第 9 章”,“深度强化学习”中所讨论的,智能体位于环境中,处于状态`s[t]`中,它是状态空间`S`的一个元素。 状态空间`S`可以是离散的,也可以是连续的。 智能体通过遵循策略`π(a[t], s[t])`从动作空间`A`采取动作`a[t]`。 `A`可以是离散的或连续的。 作为执行动作`a[t]`的结果,智能体会收到奖励`r[t + 1]`,并且环境转换为新状态`s[t + 1]`。 新状态仅取决于当前状态和操作。 智能体的目标是学习一种最佳策略`π*`,该策略可最大化所有状态的回报: - -![](img/B14853_10_009.png) (Equation 9.1.1) - -收益`R[t]`定义为从时间`t`直到剧集结束或达到最终状态时的折扣累积奖励: - -![](img/B14853_10_010.png) (Equation 9.1.2) - -根据“公式 9.1.2”,还可以通过遵循策略`π`将返回解释为给定状态的值。 从“公式 9.1.1”可以看出,与通常的`γ^k < 1.0`相比,与立即奖励相比,未来奖励的权重较低。 - -到目前为止,我们仅考虑通过优化基于值的函数`Q(s, a)`来学习策略。 - -本章的目标是通过参数化`π(a[t] | s[t]) -> π(a[t] | s[t], θ)`直接学习该策略。 通过参数化,我们可以使用神经网络来学习策略函数。 - -学习策略意味着我们将最大化某个目标函数`J(θ)`,这是相对于参数`θ`的一种表现度量。在间歇式强化学习中,表现度量是起始状态的值。 在连续的情况下,目标函数是平均奖励率。 - -通过执行梯度上升来最大化目标函数`J(θ)`。 在梯度上升中,梯度更新是在要优化的函数的导数方向上。 到目前为止,我们的所有损失函数都通过最小化或通过执行梯度下降进行了优化。 稍后,在`tf.keras`实现中,我们将看到可以通过简单地否定目标函数并执行梯度下降来执行梯度上升。 - -直接学习策略的好处是它可以应用于离散和连续动作空间。 对于离散的动作空间: - -![](img/B14853_10_019.png) (Equation 10.1.1) - -其中`a[i]`是第`i`个动作。 `a[i]`可以是神经网络的预测或状态作用特征的线性函数: - -![](img/B14853_10_022.png) (Equation 10.1.2) - -`φ(s[t], a[i])`是将状态操作转换为特征的任何函数,例如编码器。 - -`π(a[t] | s[t], θ)`确定每个`a[i]`的概率。 例如,在上一章中的柱杆平衡问题中,目标是通过沿二维轴向左或向右移动柱车来保持柱子直立。 在这种情况下,`a[0]`和`a[1]`分别是左右移动的概率。 通常,智能体以最高概率`a[t] = max[i] π(a[t] | s[t], θ)`采取行动。 - -对于连续动作空间,`π(a[t] | s[t], θ)`根据给定状态的概率分布对动作进行采样。 例如,如果连续动作空间在`a[t] ∈ [-1.0, 1.0]`范围内,则`π(a[t] | s[t], θ)`通常是高斯分布,其均值和标准差由策略网络预测。 预测动作是来自此高斯分布的样本。 为了确保不会生成任何无效的预测,该操作将被限制在 -1.0 和 1.0 之间。 - -正式地,对于连续的动作空间,该策略是高斯分布的样本: - -![](img/B14853_10_032.png) (Equation 10.1.3) - -平均值`μ`和标准差`σ`都是状态特征的函数: - -![](img/B14853_10_035.png) (Equation 10.1.4) - -![](img/B14853_10_036.png) (Equation 10.1.5) - -`φ(s[i])`是将状态转换为其特征的任何函数。 `ζ(x) = log(1 + e^x)`是确保标准差为正值的`softplus`函数。 实现状态特征函数`φ(s[t])`的一种方法是使用自编码器网络的编码器。 在本章的最后,我们将训练一个自编码器,并将编码器部分用作状态特征。 因此,训练策略网络是优化参数的问题`θ = [θ[μ], θ[σ]]`。 - -给定连续可微分的策略函数`π(a[t] | s[t], θ)`,策略梯度可以计算为: - -![](img/B14853_10_042.png) (Equation 10.1.6) - -“公式 10.1.6”也被称为*策略梯度定理*。 它适用于离散和连续动作空间。 根据通过 Q 值缩放的策略操作采样的自然对数,可以计算出相对于参数`θ`的梯度。“公式 10.1.6”利用了自然对数`ᐁx/x = ᐁlnx`的特性。 - -策略梯度定理在某种意义上是直观的,即表现梯度是根据目标策略样本估计的,并且与策略梯度成比例。 策略梯度由 Q 值缩放,以鼓励对状态值产生积极贡献的行动。 梯度还与动作概率成反比,以惩罚对提高性能没有贡献的频繁发生的动作。 - -有关策略梯度定理的证明,请参阅[2]和 [David Silver 关于强化学习的讲义](http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching_files/pg.pdf)。 - -与策略梯度方法相关的细微优势。 例如,在某些基于纸牌的游戏中,与基于策略的方法不同,基于值的方法在处理随机性方面没有直接的过程。 在基于策略的方法中,操作概率随参数而平滑变化。 - -同时,相对于参数的微小变化,基于值的行为可能会发生剧烈变化。 最后,基于策略的方法对参数的依赖性使我们对如何执行表现考核的梯度提升产生了不同的表述。 这些是在后续部分中介绍的四种策略梯度方法。 - -基于策略的方法也有其自身的缺点。 由于趋向于收敛于局部最优而非全局最优,所以它们通常更难训练。 在本章末尾提出的实验中,智能体很容易适应并选择不一定提供最高值的动作。 策略梯度的特征还在于高差异。 - -梯度更新经常被高估。 此外,基于训练策略的方法非常耗时。 训练需要成千上万集(即采样效率不高)。 每个剧集仅提供少量样本。 在本章结尾处提供的实现方面的典型训练,大约需要一个小时才能在 GTX 1060 GPU 上进行 1,000 集。 - -在以下各节中,我们将讨论四种策略梯度方法。 虽然讨论的重点是连续的动作空间,但是该概念通常适用于离散的动作空间。 - -# 2\. 蒙特卡洛策略梯度(REINFORCE)方法 - -最简单的策略梯度方法是 REINFORCE [4],这是蒙特卡洛策略梯度方法: - -![](img/B14853_10_045.png) (Equation 10.2.1) - -其中`R[t]`是返回值,如“公式 9.1.2”所定义。`R[t]`是策略梯度定理中`Q^π(s[t], a[t])`的无偏样本。 - -“算法 10.2.1”总结了 REINFORCE 算法[2]。 REINFORCE 是一种蒙特卡洛算法。 它不需要环境动态知识(换句话说,无需模型)。 仅需要经验样本`(s[i], a[i], r[i+1], s[i+1])`来优化策略网络`π(a[t] | s[t])`的参数。 折扣因子`γ`考虑到奖励随着步数增加而降低的事实。 梯度被`γ^k`打折。 在后续步骤中采用的梯度贡献较小。 学习率`α`是梯度更新的比例因子。 - -通过使用折扣梯度和学习率执行梯度上升来更新参数。 作为蒙特卡洛算法,REINFORCE 要求智能体在处理梯度更新之前先完成一集。 同样由于其蒙特卡洛性质,REINFORCE 的梯度更新具有高方差的特征。 - -**算法 10.2.1 REINFORCE** - -*要求*:可微分的参数化目标策略网络`π(a[t] | s[t], θ)`。 - -*要求*:折扣因子,`γ = [0, 1]`和学习率`α`。 例如,`γ = 0.99`和`α = 1e - 3`。 - -*要求*:`θ[0]`,初始策略网络参数(例如,`θ[0] -> 0`)。 - -1. 重复。 -2. 通过跟随`π(a[t] | s[t], θ)`来生成剧集`(s[0]a[0]r[1]s[1], s[1]a[1]r[2]s[2], ..., s[T-1]a[T-1]r[T]s[T])`。 -3. 对于步骤`t = 0, ..., T - 1`,执行: -4. 计算返回值`R[t] = Σ γ^t r[t+k], k = 0, ..., T`。 -5. 计算折扣的表现梯度`ᐁJ(θ) = r^t R[t] ᐁ[θ] ln π(a[t] | s[t], θ)`。 - -1. 执行梯度上升`θ = θ + αᐁJ(θ)`。 - -在 REINFORCE 中,可以通过神经网络对参数化策略进行建模,如图“图 10.2.1”所示: - -![](img/B14853_10_01.png) - -图 10.2.1:策略网络 - -如上一节中讨论的,在连续动作空间的情况下,状态输入被转换为特征。 状态特征是策略网络的输入。 代表策略函数的高斯分布具有均值和标准差,均是状态特征的函数。 根据状态输入的性质,策略网络`π(θ)`可以是 MLP,CNN 或 RNN。 预测的动作只是策略函数的样本。 - -“列表 10.2.1”显示了`REINFORCEAgent` 类,该类在`tf.keras`中实现了“算法 10.2.1”。 `train_by_episode()`在剧集完成后调用,以计算每个步骤的回报。 `train()`通过针对目标函数`logp_model`优化网络来执行“算法 10.2.1”的第 5 行和第 6 行。 父类`PolicyAgent`在本章介绍的四种策略梯度方法的算法中实现了的通用代码。 在讨论所有策略梯度方法之后,将介绍`PolicyAgent`。 - -“列表 10.2.1”:`policygradient-car-10.1.1.py` - -```py -class REINFORCEAgent(PolicyAgent): - def __init__(self, env): - """Implements the models and training of - REINFORCE policy gradient method - Arguments: - env (Object): OpenAI gym environment - """ - super().__init__(env) -``` - -```py - def train_by_episode(self): - """Train by episode - Prepare the dataset before the step by step training - """ - # only REINFORCE and REINFORCE with baseline - # use the ff code - # convert the rewards to returns - rewards = [] - gamma = 0.99 - for item in self.memory: - [_, _, _, reward, _] = item - rewards.append(reward) - - # compute return per step - # return is the sum of rewards from t til end of episode - # return replaces reward in the list - for i in range(len(rewards)): - reward = rewards[i:] - horizon = len(reward) - discount = [math.pow(gamma, t) for t in range(horizon)] - return_ = np.dot(reward, discount) - self.memory[i][3] = return_ -``` - -```py - # train every step - for item in self.memory: - self.train(item, gamma=gamma) -``` - -```py - def train(self, item, gamma=1.0): - """Main routine for training - Arguments: - item (list) : one experience unit - gamma (float) : discount factor [0,1] - """ - [step, state, next_state, reward, done] = item -``` - -```py - # must save state for entropy computation - self.state = state -``` - -```py - discount_factor = gamma**step - delta = reward -``` - -```py - # apply the discount factor as shown in Algorithms - # 10\. 2.1, 10.3.1 and 10.4.1 - discounted_delta = delta * discount_factor - discounted_delta = np.reshape(discounted_delta, [-1, 1]) - verbose = 1 if done else 0 -``` - -```py - # train the logp model (implies training of actor model - # as well) since they share exactly the same set of - # parameters - self.logp_model.fit(np.array(state), - discounted_delta, - batch_size=1, - epochs=1, - verbose=verbose) -``` - -以下部分提出了对 REINFORCE 方法的改进。 - -# 3\. 带基线方法的 REINFORCE - -REINFORCE 算法可以通过从收益`δ = R[t] - B(s[t])`中减去基线来概括。 基线函数`B(s[t])`可以是任何函数,只要它不依赖`a[t]`即可。 基线不会改变表现梯度的期望: - -![](img/B14853_10_069.png) (Equation 10.3.1) - -“公式 10.3.1”隐含`E[π] [B(s[t]) ᐁ[θ] ln π(a[t] | s[t], θ)] = 0`,因为`B(s[t])`不是`a[t]`的函数。 尽管引入基准不会改变期望值,但会减小梯度更新的方差。 方差的减少通常会加速学习。 - -在大多数情况下,我们使用值函数`B(s[t]) = V(s[t])`作为基准。 如果收益被高估,则比例系数将通过值函数成比例地减小,从而导致较低的方差。 值函数也已参数化`V(s[t]) = V(s[t]; θ[v])`,并与策略网络一起进行了训练。 在连续动作空间中,状态值可以是状态特征的线性函数: - -![](img/B14853_10_075.png) (Equation 10.3.2) - -“算法 10.3.1”用基线方法[1]总结了 REINFORCE。 这与 REINFORCE 相似,只不过将返回值替换为`δ`。 区别在于我们现在正在训练两个神经网络。 - -算法 10.3.1 带基线的 REINFORCE - -*要求*:可微分的参数化目标策略网络`π(a[t] | s[t], θ)`。 - -*要求*:可微分的参数化值网络`V(s[t], θ[v])`。 - -*要求*:折扣因子`γ ∈ [0, 1]`,表现梯度的学习率`α`和值梯度`α[v]`的学习率。 - -*要求*:`θ[0]`,初始策略网络参数(例如,`θ[0] -> 0`)。 `θ[v0]`,初始值网络参数(例如`θ[v0] -> 0`)。 - -1. 重复。 -2. 通过跟随`π(a[t] | s[t], θ)`来生成剧集`(s[0]a[0]r[1]s[1], s[1]a[1]r[2]s[2], ..., a[T-1]a[T-1]r[T]s[T])`。 -3. 对于步骤`t = 0, ..., T - 1`,执行: -4. 计算返回值: - - ![](img/B14853_10_062.png) -5. 减去基线: - - ![](img/B14853_10_090.png) -6. 计算折扣值梯度: - - ![](img/B14853_10_091.png) -7. 执行梯度上升: - - ![](img/B14853_10_092.png) -8. 计算折扣的表现梯度: - - ![](img/B14853_10_093.png) - -1. 执行梯度上升: - - ![](img/B14853_10_064.png) - -如图“图 10.3.1”所示,除了策略网络`π(θ)`之外,值网络`V(θ)`也同时受到训练。 通过表现梯度`ᐁJ(θ)`更新策略网络参数,而通过梯度`ᐁV(θ[v])`调整值网络参数。 由于 REINFORCE 是蒙特卡罗算法,因此值函数训练也是蒙特卡罗算法。 - -学习率不一定相同。 请注意,值网络也在执行梯度上升。 - -![](img/B14853_10_02.png) - -图 10.3.1:策略和值网络。 具有基线的 REINFORCE 具有一个计算基线的值网络 - -“列表 10.3.1”显示了`REINFORCEBaselineAgent`类,该类在`tf.keras`中实现了“算法 10.3.1”。 它继承自`REINFORCEAgent`,因为这两种算法仅在和`train()`方法上有所不同。 “算法 10.3.1”的第 5 行由`delta = reward - self.value(state)[0]`计算。 然后,通过调用各自模型的`fit()`方法来优化第 7 行和第 9 行中用于目标和值函数的网络`logp_model`和`value_model`。 - -“列表 10.3.1”:`policygradient-car-10.1.1.py` - -```py -class REINFORCEBaselineAgent(REINFORCEAgent): - def __init__(self, env): - """Implements the models and training of - REINFORCE w/ baseline policy - gradient method - Arguments: - env (Object): OpenAI gym environment - """ - super().__init__(env) -``` - -```py - def train(self, item, gamma=1.0): - """Main routine for training - Arguments: - item (list) : one experience unit - gamma (float) : discount factor [0,1] - """ - [step, state, next_state, reward, done] = item -``` - -```py - # must save state for entropy computation - self.state = state -``` - -```py - discount_factor = gamma**step -``` - -```py - # reinforce-baseline: delta = return - value - delta = reward - self.value(state)[0] -``` - -```py - # apply the discount factor as shown in Algorithms - # 10\. 2.1, 10.3.1 and 10.4.1 - discounted_delta = delta * discount_factor - discounted_delta = np.reshape(discounted_delta, [-1, 1]) - verbose = 1 if done else 0 -``` - -```py - # train the logp model (implies training of actor model - # as well) since they share exactly the same set of - # parameters - self.logp_model.fit(np.array(state), - discounted_delta, - batch_size=1, - epochs=1, - verbose=verbose) -``` - -```py - # train the value network (critic) - self.value_model.fit(np.array(state), - discounted_delta, - batch_size=1, - epochs=1, - verbose=verbose) -``` - -在的下一部分中,我们将介绍使用基准线方法对 REINFORCE 的改进。 - -# 4\. 演员评论家方法 - -在带有基线的 REINFORCE 方法中,该值用作基线。 它不用于训练值函数。 在本节中,我们介绍 REINFORCE 与基线的变化,称为演员评论家方法。 策略和值网络扮演着参与者和批评者网络的角色。 策略网络是参与者决定给定状态时要采取的操作。 同时,值网络评估参与者或策略网络做出的决策。 - -值网络充当批评者的角色,可以量化参与者所选择的行动的好坏。 值网络通过将状态值`V(s, θ[v]`与收到的奖励`r`和观察到的下一个状态`γV(s', θ[v])`的折扣值之和来评估状态值。 差异`δ`表示为: - -![](img/B14853_10_103.png) (Equation 10.4.1) - -为了简单起见,我们在中删除了`r`和`s`的下标。“公式 10.4.1”类似于“第 9 章”,“深度强化学习”中讨论的 Q 学习中的时间差异。 下一个状态值被`γ = [0.0, 1.0]`折扣。估计遥远的未来奖励很困难。 因此,我们的估计仅基于近期`r + γV(s', θ[v])`。 这就是*自举*技术。 - -自举技术和“公式 10.4.1”中状态表示的依赖性通常会加速学习并减少差异。 从“公式 10.4.1”,我们注意到值网络评估了当前状态`s = s[t]`,这是由于策略网络的上一个操作`a[t-1]`。 同时,策略梯度基于当前动作`a[t]`。 从某种意义上说,评估延迟了一步。 - -“算法 10.4.1”总结了演员评论家方法[1]。 除了评估用于训练策略和值网络的状态值评估外,还可以在线进行训练。 在每个步骤中,两个网络都经过训练。 这与 REINFORCE 和带有基线的 REINFORCE 不同,在基线之前,智能体完成了一个剧集。 首先,在当前状态的值估计期间向值网络查询两次,其次,为下一个状态的值查询。 这两个值都用于梯度计算中。 - -**算法 10.4.1 演员评论家** - -*要求*:可微分的参数化目标策略网络`π(a | s, θ)`。 - -*要求*:可微分的参数化值网络`V(s, θ[v])`。 - -*要求*:折扣因子`γ ∈ [0, 1]`,表现梯度的学习率`α`和值梯度`α[v]`的学习率。 - -*要求*:`θ[0]`,初始策略网络参数(例如,`θ[0] -> 0`)。 `θ[v0]`,初始值网络参数(例如`θ[v0] -> 0`)。 - -1. 重复。 -2. 对于步骤`t = 0, ..., T - 1`,执行: -3. 对动作`a ~ π(a | s, θ)`进行采样。 -4. 执行动作并观察奖励`r`和下一个状态`s'`。 -5. 评估状态值估计: - - ![](img/B14853_10_121.png) -6. 计算折扣值梯度: - - ![](img/B14853_10_122.png) -7. 执行梯度上升: - - ![](img/B14853_10_092.png) -8. 计算折扣表现梯度: - - ![](img/B14853_10_124.png) -9. 执行梯度上升: - - ![](img/B14853_10_064.png) - -1. `s = s'` - -“图 10.4.1”显示了演员评论家网络: - -![](img/B14853_10_03.png) - -图 10.4.1:演员评论家网络。 通过对值`V'`的第二次评估,演员评论家与 REINFORCE 的基线有所不同 - -“列表 10.4.1”显示了`ActorCriticAgent`类,该类在`tf.keras`中实现了“算法 10.4.1”。 与两种 REINFORCE 方法不同,演员评论家不等待剧集完成。 因此,它没有实现`train_by_episode()`。 在每个体验单元,通过调用各自模型的`fit()`方法,优化第 7 行和第 9 行中用于目标和值函数`logp_model`和`value_model`的网络。 `delta`变量存储第 5 行的结果。 - -“列表 10.4.1”:`policygradient-car-10.1.1.py` - -```py -class ActorCriticAgent(PolicyAgent): - def __init__(self, env): - """Implements the models and training of - Actor Critic policy gradient method - Arguments: - env (Object): OpenAI gym environment - """ - super().__init__(env) -``` - -```py - def train(self, item, gamma=1.0): - """Main routine for training - Arguments: - item (list) : one experience unit - gamma (float) : discount factor [0,1] - """ - [step, state, next_state, reward, done] = item -``` - -```py - # must save state for entropy computation - self.state = state -``` - -```py - discount_factor = gamma**step -``` - -```py - # actor-critic: delta = reward - value - # + discounted_next_value - delta = reward - self.value(state)[0] -``` - -```py - # since this function is called by Actor-Critic - # directly, evaluate the value function here - if not done: - next_value = self.value(next_state)[0] - # add the discounted next value - delta += gamma*next_value -``` - -```py - # apply the discount factor as shown in Algortihms - # 10\. 2.1, 10.3.1 and 10.4.1 - discounted_delta = delta * discount_factor - discounted_delta = np.reshape(discounted_delta, [-1, 1]) - verbose = 1 if done else 0 -``` - -```py - # train the logp model (implies training of actor model - # as well) since they share exactly the same set of - # parameters - self.logp_model.fit(np.array(state), - discounted_delta, - batch_size=1, - epochs=1, - verbose=verbose) -``` - -最终的策略梯度方法是 A2C。 - -# 5\. 优势演员评论家(A2C)方法 - -在上一节的演员评论家方法中,目标是使的值函数正确评估状态值。 还有其他用于训练值网络的技术。 一种明显的方法是在值函数优化中使用**均方误差**(**MSE**),类似于 Q 学习中的算法。 新值梯度等于返回值`R[t]`与状态值之间的 MSE 偏导数: - -![](img/B14853_10_127.png) (Equation 10.5.1) - -作为`(R[t] - V(s, θ[v])) -> 0`,值网络预测在预测给定状态的收益时变得更加准确。 我们将演员评论家算法的这种变化称为“优势演员评论家(A2C)”。 A2C 是[3]提出的“异步优势参与者关键(A3C)”的单线程或同步版本。 数量`R[t] - V(s, θ[v])`被称为*优势*。 - -“算法 10.5.1”总结了 A2C 方法。 A2C 和演员评论家之间存在一些差异。演员评论家在线上或根据经验样本进行训练。 A2C 类似于带基线的蒙特卡洛算法,REINFORCE 和 REINFORCE。 一集完成后,将对其进行训练。 从第一个状态到最后一个状态都对演员评论家进行了训练。 A2C 训练从最后一个状态开始,并在第一个状态结束。 此外,`γ^t`不再打折 A2C 策略和值梯度。 - -A2C 的相应网络类似于“图 10.4.1”,因为我们仅更改了梯度计算方法。 为了鼓励训练过程中的探员探索,A3C 算法[3]建议将策略函数的加权熵值的梯度添加到到梯度函数`β ᐁ[θ] H(π(a[t] | s[t], θ))`中。 回想一下,熵是对信息或事件不确定性的度量。 - -**算法 10.5.1 优势演员评论家(A2C)** - -*要求*:可微分的参数化目标策略网络`π(a[t] | s[t], θ)`。 - -*要求*:可微分的参数化值网络`V(s[t], θ[v])`。 - -*要求*:折扣因子`γ ∈ [0, 1]`,表现梯度的学习率`α`,值梯度的学习率`α[v]`和熵权`β`。 - -*要求*:`θ[0]`,初始策略网络参数(例如,`θ[0] -> 0`)。 `θ[v0]`,初始值网络参数(例如`θ[v0] -> 0`)。 - -1. 重复。 -2. 通过跟随`π(a[t] | s[t], θ)`来生成剧集`(s[0]a[0]r[1]s[1], s[1]a[1]r[2]s[2], ..., a[T-1]a[T-1]r[T]s[T])`。 -3. ![](img/B14853_10_144.png) -4. 对于步骤`t = 0, ..., T - 1`,执行: -5. 计算返回值: - - ![](img/B14853_10_146.png) -6. 计算值梯度: - - ![](img/B14853_10_147.png) -7. 累积梯度: - - ![](img/B14853_10_092.png) -8. 计算表现梯度: - - ![](img/B14853_10_149.png) - -1. 执行梯度上升: - - ![](img/B14853_10_064.png) - -“列表 10.5.1”显示了`A2CAgent`类,该类在`tf.keras`中实现了“算法 10.5.1”。 与两个 REINFORCE 方法不同,返回值是从最后一个体验单元或状态到第一个体验单元或状态的计算得出的。 在每个体验单元,通过调用各自模型的`fit()`方法,优化第 7 行和第 9 行中用于目标和值函数`logp_model`和`value_model`的网络。 注意,在对象实例化期间,熵损失的`beta`或权重设置为`0.9`,以指示将使用熵损失函数。 此外,使用 MSE 损失函数训练`value_model`。 - -“列表 10.5.1”:`policygradient-car-10.1.1.py` - -```py -class A2CAgent(PolicyAgent): - def __init__(self, env): - """Implements the models and training of - A2C policy gradient method - Arguments: - env (Object): OpenAI gym environment - """ - super().__init__(env) - # beta of entropy used in A2C - self.beta = 0.9 - # loss function of A2C value_model is mse - self.loss = 'mse' -``` - -```py - def train_by_episode(self, last_value=0): - """Train by episode - Prepare the dataset before the step by step training - Arguments: - last_value (float): previous prediction of value net - """ - # implements A2C training from the last state - # to the first state - # discount factor - gamma = 0.95 - r = last_value - # the memory is visited in reverse as shown - # in Algorithm 10.5.1 - for item in self.memory[::-1]: - [step, state, next_state, reward, done] = item - # compute the return - r = reward + gamma*r - item = [step, state, next_state, r, done] - # train per step - # a2c reward has been discounted - self.train(item) -``` - -```py - def train(self, item, gamma=1.0): - """Main routine for training - Arguments: - item (list) : one experience unit - gamma (float) : discount factor [0,1] - """ - [step, state, next_state, reward, done] = item -``` - -```py - # must save state for entropy computation - self.state = state -``` - -```py - discount_factor = gamma**step -``` - -```py - # a2c: delta = discounted_reward - value - delta = reward - self.value(state)[0] -``` - -```py - verbose = 1 if done else 0 -``` - -```py - # train the logp model (implies training of actor model - # as well) since they share exactly the same set of - # parameters - self.logp_model.fit(np.array(state), - discounted_delta, - batch_size=1, - epochs=1, - verbose=verbose) -``` - -```py - # in A2C, the target value is the return (reward - # replaced by return in the train_by_episode function) - discounted_delta = reward - discounted_delta = np.reshape(discounted_delta, [-1, 1]) -``` - -```py - # train the value network (critic) - self.value_model.fit(np.array(state), - discounted_delta, - batch_size=1, - epochs=1, - verbose=verbose) -``` - -在介绍的四种算法中,它们仅在目标函数和值(如果适用)优化方面有所不同。 在下一节中,我们将介绍四种算法的统一代码。 - -# 6\. 使用 Keras 的策略梯度方法 - -上一节中讨论的策略梯度方法(“算法 10.2.1”至“算法 10.5.1”)使用相同的策略和值网络模型。“图 10.2.1”至“图 10.4.1”中的策略和值网络具有相同的配置。 四种策略梯度方法的不同之处仅在于: - -* 表现和值梯度公式 -* 训练策略 - -在本节中,我们将以一个代码讨论`tf.keras`算法 10.2.1 至“算法 10.5.1”的通用例程在`tf.keras`中的实现。 - -完整的代码可以在[这个页面](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras)中找到。 - -但是在讨论实现之前,让我们简要探讨训练环境。 - -与 Q 学习不同,策略梯度方法适用于离散和连续动作空间。 在我们的示例中,我们将在连续动作空间案例示例中演示四种策略梯度方法,例如 OpenAI 健身房的[`MountainCarContinuous-v0`](https://gym.openai.com)。 如果您不熟悉 OpenAI Gym,请参阅“第 9 章”,“深度强化学习”。 - -“图 10.6.1”中显示了`MountainCarContinuous-v0`二维环境的快照。在此二维环境中,一辆功率不太强的汽车停在两座山之间: - -![](img/B14853_10_04.png) - -图 10.6.1:`MountainCarContinuous-v0` OpenAI Gym 环境 - -为了到达右侧山顶的黄旗,它必须来回行驶以获得足够的动力。 应用于汽车的能量越多(即动作的绝对值越大),则奖励越小(或负作用越大)。 - -奖励始终为负,到达标志时仅为正。 在这种情况下,汽车将获得 +100 的奖励。 但是,每个操作都会受到以下代码的惩罚: - -```py -reward-= math.pow(action[0],2)*0.1 -``` - -有效动作值的连续范围是`[-1.0, 1.0]`。 超出范围时,动作将被剪裁为其最小值或最大值。 因此,应用大于 1.0 或小于 -1.0 的操作值是没有意义的。 - -`MountainCarContinuous-v0`环境状态包含两个元素: - -* 车厢位置 -* 车速 - -通过编码器将状态转换为状态特征。 像动作空间一样,状态空间也是连续的。 预测的动作是给定状态的策略模型的输出。 值函数的输出是状态的预测值。 - -如图“图 10.2.1”到“图 10.4.1”所示,在建立策略和值网络之前,我们必须首先创建一个将状态转换为特征的函数。 该函数由自编码器的编码器实现,类似于在“第 3 章”,“自编码器”中实现的编码器。 - -“图 10.6.2”显示了包括编码器和解码器的自编码器: - -![A screenshot of a cell phone Description automatically generated](img/B14853_10_05.png) - -图 10.6.2:自编码器模型 - -在“图 10.6.3”中,编码器是由`Input(2)-Dense(256, activation='relu')-Dense(128, activation='relu')-Dense(32)`制成的 MLP。 每个状态都转换为 32 维特征向量: - -![A screenshot of a cell phone Description automatically generated](img/B14853_10_06.png) - -图 10.6.3:编码器模型 - -在“图 10.6.4”中,解码器也是 MLP,但由`Input(32)-Dense(128, activation='relu')-Dense(256, activation='relu')-Dense(2)`制成: - -![A screenshot of a cell phone Description automatically generated](img/B14853_10_07.png) - -图 10.6.4:解码器模型 - -自编码器使用 MSE,损失函数和`tf.keras`默认的 Adam 优化器训练了 10 个周期。 我们为训练和测试数据集采样了 220,000 个随机状态,并应用了 200,000:20,000 个训练测试拆分。 训练后,将保存编码器权重,以备将来在策略和值网络的训练中使用。“列表 10.6.1”显示了构建和训练自编码器的方法。 - -在`tf.keras`实现中,除非另有说明,否则我们将在本节中提及的所有例程均作为`PolicyAgent`类中的方法实现。 `PolicyAgent`的作用是代表策略梯度方法的常用功能,包括建立和训练自编码器网络模型以及预测动作,对数概率,熵和状态值。 这是“列表 10.2.1”至“列表 10.5.1”中介绍的每个策略梯度方法智能体类的超类。 - -“列表 10.6.1”:`policygradient-car-10.1.1.py` - -构建和训练特征自编码器的方法: - -```py -class PolicyAgent: - def __init__(self, env): - """Implements the models and training of - Policy Gradient Methods - Argument: - env (Object): OpenAI gym environment - """ -``` - -```py - self.env = env - # entropy loss weight - self.beta = 0.0 - # value loss for all policy gradients except A2C - self.loss = self.value_loss -``` - -```py - # s,a,r,s' are stored in memory - self.memory = [] -``` - -```py - # for computation of input size - self.state = env.reset() - self.state_dim = env.observation_space.shape[0] - self.state = np.reshape(self.state, [1, self.state_dim]) - self.build_autoencoder() -``` - -```py - def build_autoencoder(self): - """autoencoder to convert states into features - """ - # first build the encoder model - inputs = Input(shape=(self.state_dim, ), name='state') - feature_size = 32 - x = Dense(256, activation='relu')(inputs) - x = Dense(128, activation='relu')(x) - feature = Dense(feature_size, name='feature_vector')(x) -``` - -```py - # instantiate encoder model - self.encoder = Model(inputs, feature, name='encoder') - self.encoder.summary() - plot_model(self.encoder, - to_file='encoder.png', - show_shapes=True) -``` - -```py - # build the decoder model - feature_inputs = Input(shape=(feature_size,), - name='decoder_input') - x = Dense(128, activation='relu')(feature_inputs) - x = Dense(256, activation='relu')(x) - outputs = Dense(self.state_dim, activation='linear')(x) -``` - -```py - # instantiate decoder model - self.decoder = Model(feature_inputs, - outputs, - name='decoder') - self.decoder.summary() - plot_model(self.decoder, - to_file='decoder.png', - show_shapes=True) -``` - -```py - # autoencoder = encoder + decoder - # instantiate autoencoder model - self.autoencoder = Model(inputs, - self.decoder(self.encoder(inputs)), - name='autoencoder') - self.autoencoder.summary() - plot_model(self.autoencoder, - to_file='autoencoder.png', - show_shapes=True) -``` - -```py - # Mean Square Error (MSE) loss function, Adam optimizer - self.autoencoder.compile(loss='mse', optimizer='adam') -``` - -```py - def train_autoencoder(self, x_train, x_test): - """Training the autoencoder using randomly sampled - states from the environment - Arguments: - x_train (tensor): autoencoder train dataset - x_test (tensor): autoencoder test dataset - """ -``` - -```py - # train the autoencoder - batch_size = 32 - self.autoencoder.fit(x_train, - x_train, - validation_data=(x_test, x_test), - epochs=10, - batch_size=batch_size) -``` - -在给定`MountainCarContinuous-v0`环境的情况下,策略(或参与者)模型会预测必须应用于汽车的操作。 如本章第一部分中有关策略梯度方法的讨论所述,对于连续动作空间,策略模型从高斯分布`π(a[t] | s[t], θ) = a[t] ~ N(μ(s[t]), σ²(s[t]))`中采样一个动作。 在`tf.` `keras`中,实现为: - -```py -import tensorflow_probability as tfp - def action(self, args): - """Given mean and stddev, sample an action, clip - and return - We assume Gaussian distribution of probability - of selecting an action given a state - Arguments: - args (list) : mean, stddev list - """ - mean, stddev = args - dist = tfp.distributions.Normal(loc=mean, scale=stddev) - action = dist.sample(1) - action = K.clip(action, - self.env.action_space.low[0], - self.env.action_space.high[0]) - return action -``` - -动作被限制在其最小和最大可能值之间。 在这种方法中,我们使用`TensorFlow probability`包。 可以通过以下方式单独安装: - -```py -pip3 install --upgrade tensorflow-probability -``` - -策略网络的作用是预测高斯分布的均值和标准差。“图 10.6.5”显示了为`π(a[t] | s[t], θ)`建模的策略网络。 - -![A close up of text on a white background Description automatically generated](img/B14853_10_08.png) - -图 10.6.5:策略模型(参与者模型) - -请注意,编码器模型具有冻结的预训练权重。 仅平均值和标准差权重会收到表现梯度更新。 策略网络基本上是“公式 10.1.4”和“公式 10.1.5”的实现,为方便起见在此重复: - -![](img/B14853_10_153.png) (Equation 10.1.4) - -![](img/B14853_10_036.png) (Equation 10.1.5) - -其中`φ(s[t])`是编码器,`θ[μ]`是平均值`Dense(1)`层的权重,`θ[σ]`是标准差`Dense(1)`层的权重。 我们使用修改后的`softplus`函数`ζ(·)`来避免标准差为零: - -```py -def softplusk(x): - """Some implementations use a modified softplus - to ensure that the stddev is never zero - Argument: - x (tensor): activation input - """ - return K.softplus(x) + 1e-10 -``` - -策略模型构建器显示在“列表 10.6.2”中。 对数概率,熵和值模型也包含在此清单中,我们将在下面讨论。 - -“列表 10.6.2”:`policygradient-car-10.1.1.py` - -根据编码后的状态特征构建策略(角色),`logp`,熵和值模型的方法: - -```py - def build_actor_critic(self): - """4 models are built but 3 models share the - same parameters. hence training one, trains the rest. - The 3 models that share the same parameters - are action, logp, and entropy models. - Entropy model is used by A2C only. - Each model has the same MLP structure: - Input(2)-Encoder-Output(1). - The output activation depends on the nature - of the output. - """ - inputs = Input(shape=(self.state_dim, ), name='state') - self.encoder.trainable = False - x = self.encoder(inputs) - mean = Dense(1, - activation='linear', - kernel_initializer='zero', - name='mean')(x) - stddev = Dense(1, - kernel_initializer='zero', - name='stddev')(x) - # use of softplusk avoids stddev = 0 - stddev = Activation('softplusk', name='softplus')(stddev) - action = Lambda(self.action, - output_shape=(1,), - name='action')([mean, stddev]) - self.actor_model = Model(inputs, action, name='action') - self.actor_model.summary() - plot_model(self.actor_model, - to_file='actor_model.png', - show_shapes=True) -``` - -```py - logp = Lambda(self.logp, - output_shape=(1,), - name='logp')([mean, stddev, action]) - self.logp_model = Model(inputs, logp, name='logp') - self.logp_model.summary() - plot_model(self.logp_model, - to_file='logp_model.png', - show_shapes=True) -``` - -```py - entropy = Lambda(self.entropy, - output_shape=(1,), - name='entropy')([mean, stddev]) - self.entropy_model = Model(inputs, entropy, name='entropy') - self.entropy_model.summary() - plot_model(self.entropy_model, - to_file='entropy_model.png', - show_shapes=True) -``` - -```py - value = Dense(1, - activation='linear', - kernel_initializer='zero', - name='value')(x) - self.value_model = Model(inputs, value, name='value') - self.value_model.summary() - plot_model(self.value_model, - to_file='value_model.png', - show_shapes=True) -``` - -```py - # logp loss of policy network - loss = self.logp_loss(self.get_entropy(self.state), - beta=self.beta) - optimizer = RMSprop(lr=1e-3) - self.logp_model.compile(loss=loss, optimizer=optimizer) -``` - -```py - optimizer = Adam(lr=1e-3) - self.value_model.compile(loss=self.loss, optimizer=optimizer) -``` - -![](img/B14853_10_09.png) - -图 10.6.6:策略的高斯对数概率模型 - -除了策略网络`π(a[t] | s[t], θ)`之外,我们还必须具有操作日志概率(`logp`)网络`ln π(a[t] | s[t], θ)`,因为该实际上是计算梯度的系统。 如图“图 10.6.6”所示,`logp`网络只是一个策略网络,其中附加的 Lambda(1)层在给定了作用,均值和标准差的情况下计算了高斯分布的对数概率。 - -`logp`网络和参与者(策略)模型共享同一组参数。 Lambda 层没有任何参数。 它是通过以下函数实现的: - -```py - def logp(self, args): - """Given mean, stddev, and action compute - the log probability of the Gaussian distribution - Arguments: - args (list) : mean, stddev action, list - """ - mean, stddev, action = args - dist = tfp.distributions.Normal(loc=mean, scale=stddev) - logp = dist.log_prob(action) - return logp -``` - -训练`logp`网络也可以训练角色模型。 在本节中讨论的训练方法中,仅训练`logp`网络。 - -如图“图 10.6.7”所示,熵模型还与策略网络共享参数: - -![](img/B14853_10_10.png) - -图 10.6.7:熵模型 - -给定平均值和标准差,使用以下函数,输出`Lambda(1)`层计算高斯分布的熵: - -```py - def entropy(self, args): - """Given the mean and stddev compute - the Gaussian dist entropy - Arguments: - args (list) : mean, stddev list - """ - mean, stddev = args - dist = tfp.distributions.Normal(loc=mean, scale=stddev) - entropy = dist.entropy() - return entropy -``` - -熵模型仅用于 A2C 方法。 - -“图 10.6.8”显示了值模型: - -![](img/B14853_10_11.png) - -图 10.6.8:值模型 - -该模型还使用具有权重的预训练编码器来实现以下公式“公式 10.3.2”,为方便起见,在此重复: - -![](img/B14853_10_161.png) (Equation 10.3.2) - -`θ[v]`是`Dense(1)`层的权重,该层是唯一接收值梯度更新的层。“图 10.6.8”表示“算法 10.3.1”至“算法 10.5.1”中的`V(s[t], θ[v])`。 值模型可以建立在以下几行中: - -```py -inputs = Input(shape=(self.state_dim, ), name='state') -self.encoder.trainable = False -x = self.encoder(inputs) -value = Dense(1, - activation='linear', - kernel_initializer='zero', - name='value')(x) -self.value_model = Model(inputs, value, name='value') -``` - -这些行也用`build_actor_critic()`方法实现,如清单 10.6.2 所示。 - -建立网络模型后,下一步就是训练。 在“算法 10.2.1”至“算法 10.5.1”中,我们通过梯度上升执行目标函数最大化。 在`tf.keras`中,我们通过梯度下降执行损失函数最小化。 损失函数只是目标函数最大化的负数。 梯度下降是梯度上升的负值。“列表 10.6.3”显示了`logp`和值损失函数。 - -我们可以利用损失函数的通用结构来统一“算法 10.2.1”至“算法 10.5.1”中的损失函数。 表现和值梯度仅在其恒定因子上有所不同。 所有表现梯度都有一个通用项`ᐁ[θ] ln π(a[t] | s[t], θ)`。 这由策略日志概率损失函数`logp_loss()`中的`y_pred`表示。 通用项`ᐁ[θ] ln π(a[t] | s[t], θ)`的因素取决于哪种算法,并实现为`y_true`。“表 10.6.1”显示`y_true`的值。 其余项是熵的加权梯度`β ᐁ[θ] H(π(a[t] | s[t], θ))`。 这是通过`logp_loss()`函数中`beta`和`entropy`的乘积实现的。 仅 A2C 使用此项,因此默认为`self.beta=0.0`。 对于 A2C,`self.beta=0.9`。 - -“列表 10.6.3”:`policygradient-car-10.1.1.py` - -`logp`和值网络的损失函数: - -```py - def logp_loss(self, entropy, beta=0.0): - """logp loss, the 3rd and 4th variables - (entropy and beta) are needed by A2C - so we have a different loss function structure - Arguments: - entropy (tensor): Entropy loss - beta (float): Entropy loss weight - """ - def loss(y_true, y_pred): - return -K.mean((y_pred * y_true) \ - + (beta * entropy), axis=-1) -``` - -```py - return loss -``` - -```py - def value_loss(self, y_true, y_pred): - """Typical loss function structure that accepts - 2 arguments only - this will be used by value loss of all methods - except A2C - Arguments: - y_true (tensor): value ground truth - y_pred (tensor): value prediction - """ - return -K.mean(y_pred * y_true, axis=-1) -``` - -| **算法** | `logp_loss`的`y_true` | `value_loss`的`y_true` | -| --- | --- | --- | -| 10.2.1 REINFORCE | `γ^t R[t]` | 不适用 | -| 10.3.1 使用基线的 REINFORCE | `γ^t δ` | `γ^t δ` | -| 10.4.1 演员评论家 | `γ^t δ` | `γ^t δ` | -| 10.5.1 A2C | `R[t] - V(s, θ[v])` | `R[t]` | - -表 10.6.1:`logp_loss`的`y_true`值和`value_loss` - -“表 10.6.2”中显示了用于计算“表 10.6.1”中的`y_true`的代码实现: - -| **算法** | `y_true`公式 | Keras 中的`y_true` | -| --- | --- | --- | -| 10.2.1 REINFORCE | `γ^t R[t]` | `reward * discount_factor` | -| 10.3.1 使用基线的 REINFORCE | `γ^t δ` | `(reward - self.value(state)[0]) * discount_factor` | -| 10.4.1 演员评论家 | `γ^t δ` | `(reward - self.value(state)[0] + gamma * next_value) * discount_factor` | -| 10.5.1 A2C | `R[t] - V(s, θ[v])`和`R[t]` | (`reward - self.value(state)[0]`)和`reward` | - -表 10.6.2:表 10.6.1 中的`y_true`值 - -类似地,“算法 10.3.1”和“算法 10.4.1”的值损失函数具有相同的结构。 值损失函数在`tf.keras`中实现为`value_loss()`,如“列表 10.6.3”所示。 公共梯度因子`ᐁ[θ[v]] V(s[t], θ[v])`由张量`y_pred`表示。 剩余因子由`y_true`表示。 `y_true`值也显示在“表 10.6.1”中。 REINFORCE 不使用值函数。 A2C 使用 MSE 损失函数来学习值函数。 在 A2C 中,`y_true`代表目标值或基本情况。 - -有了所有网络模型和损失函数,最后一部分是训练策略,每种算法都不同。 每个策略梯度方法的训练算法已在“列表 10.2.1”至“列表 10.5.1”中进行了讨论。 “算法 10.2.1”,“算法 10.3.1”和“算法 10.5.1”等待完整的剧集在训练之前完成,因此它同时运行`train_by_episode()` 和`train()`。 完整剧集保存在`self.memory`中。 演员评论家“算法 10.4.1”每步训练一次,仅运行`train()`。 - -“列表 10.6.4”显示了当智能体执行并训练策略和值模型时,一个剧集如何展开。 `for`循环执行 1,000 集。 当达到 1,000 步或汽车触及旗帜时,剧集终止。 智能体在每个步骤执行策略预测的操作。 在每个剧集或步骤之后,将调用训练例程。 - -“列表 10.6.4”:`policygradient-car-10.1.1.py` - -```py - # sampling and fitting - for episode in range(episode_count): - state = env.reset() - # state is car [position, speed] - state = np.reshape(state, [1, state_dim]) - # reset all variables and memory before the start of - # every episode - step = 0 - total_reward = 0 - done = False - agent.reset_memory() - while not done: - # [min, max] action = [-1.0, 1.0] - # for baseline, random choice of action will not move - # the car pass the flag pole - if args.random: - action = env.action_space.sample() - else: - action = agent.act(state) - env.render() - # after executing the action, get s', r, done - next_state, reward, done, _ = env.step(action) - next_state = np.reshape(next_state, [1, state_dim]) - # save the experience unit in memory for training - # Actor-Critic does not need this but we keep it anyway. - item = [step, state, next_state, reward, done] - agent.remember(item) -``` - -```py - if args.actor_critic and train: - # only actor-critic performs online training - # train at every step as it happens - agent.train(item, gamma=0.99) - elif not args.random and done and train: - # for REINFORCE, REINFORCE with baseline, and A2C - # we wait for the completion of the episode before - # training the network(s) - # last value as used by A2C - if args.a2c: - v = 0 if reward > 0 else agent.value(next_state)[0] - agent.train_by_episode(last_value=v) - else: - agent.train_by_episode() -``` - -```py - # accumulate reward - total_reward += reward - # next state is the new state - state = next_state - step += 1 -``` - -在训练期间,我们收集了数据以确定每个策略梯度算法的表现。 在下一部分中,我们总结了结果。 - -# 7\. 策略梯度方法的表现评估 - -通过训练智能体 1000 次剧集,评估了 4 种策略梯度方法。 我们将 1 次训练定义为 1,000 次训练。 第一表现度量标准是通过累计汽车在 1,000 集内达到标志的次数来衡量的。 - -在此指标中,A2C 达到该标志的次数最多,其次是 REINFORCE(具有基线,演员评论家和 REINFORCE)。 使用基线或批判者可以加速学习。 请注意,这些是训练会话,智能体会在其中不断提高其表现。 在实验中,有些情况下智能体的表现没有随时间改善。 - -第二个表现指标基于以下要求:如果每集的总奖励至少为 90.0,则认为`MountainCarContinuous-v0`已解决。 从每种方法的 5 个训练会话中,我们选择了最近 100 个剧集(第 900 至 999 集)中最高总奖励的 1 个训练会话。 - -“图 10.7.1”至“图 10.7.4”显示了在执行 1000 集时山地车到达标志的次数。 - -![](img/B14853_10_12.png) - -图 10.7.1:山车使用 REINFORCE 方法到达标志的次数 - -![](img/B14853_10_13.png) - -图 10.7.2:使用基线方法使用 REINFORCE,山地车到达标志的次数 - -![](img/B14853_10_14.png) - -图 10.7.3:使用演员评论家方法山地车到达旗帜的次数 - -![](img/B14853_10_15.png) - -图 10.7.4:山地车使用 A2C 方法到达标志的次数 - -“图 10.7.5”至“图 10.7.8”显示 1,000 集的总奖励。 - -![](img/B14853_10_16.png) - -图 10.7.5:使用 REINFORCE 方法获得的每集总奖励 - -![](img/B14853_10_17.png) - -图 10.7.6:使用带有基线方法的 REINFORCE,每集获得的总奖励。 - -![](img/B14853_10_18.png) - -图 10.7.7:使用演员评论家方法获得的每集总奖励 - -![](img/B14853_10_19.png) - -图 10.7.8:使用 A2C 方法获得的每集总奖励 - -以为基准的 REINFORCE 是唯一能够在 1,000 次训练中始终获得约 90 的总奖励的方法。 A2C 的表现仅次于第二名,但无法始终达到至少 90 分的总奖励。 - -在进行的实验中,我们使用相同的学习率`1e-3`进行对数概率和值网络优化。 折扣系数设置为 0.99(A2C 除外),以 0.95 的折扣系数更容易训练。 - -鼓励阅读器通过执行以下操作来运行受过训练的网络: - -```py -python3 policygradient-car-10.1.1.py ---encoder_weights=encoder_weights.h5 --actor_weights=actor_weights.h5 -``` - -“表 10.7.1”显示了其他运行`policygradient-car-10.1.1.py`的模式。 权重文件(即`*.h5`)可以替换为您自己的预训练权重文件。 请查阅代码以查看其他可能的选项。 - -| **目的** | **运行** | -| --- | --- | -| 从零开始训练 REINFORCE | `python3 policygradient-car-10.1.1.py` | -| 从头开始使用基线训练 REINFORCE | `python3 policygradient-car-10.1.1.py -b` | -| 从零开始训练演员评论家 | `python3 policygradient-car-10.1.1.py -a` | -| 从头开始训练 A2C | `python3 policygradient-car-10.1.1.py -c` | -| 从先前保存的权重中训练 REINFORCE | `python3 policygradient-car-10.1.1.py``--encoder-weights=encoder_weights.h5``--actor-weights=actor_weights.h5 --train` | -| 使用先前保存的权重使用基线训练 REINFORCE | `python3 policygradient-car-10.1.1.py``--encoder-weights=encoder_weights.h5``--actor-weights=actor_weights.h5``--value-weights=value_weights.h5 -b --train` | -| 使用先前保存的权重训练演员评论家 | `python3 policygradient-car-10.1.1.py``--encoder-weights=encoder_weights.h5``--actor-weights=actor_weights.h5``--value-weights=value_weights.h5 -a --train` | -| 使用先前保存的权重训练 A2C | `python3 policygradient-car-10.1.1.py``--encoder-weights=encoder_weights.h5``--actor-weights=actor_weights.h5``--value-weights=value_weights.h5 -c --train` | - -表 10.7.1:运行 policygradient-car-10.1.1.py 时的不同选项 - -最后一点,我们在`tf.keras`中对策略梯度方法的实现存在一些局限性。 例如,训练演员模型需要对动作进行重新采样。 首先对动作进行采样并将其应用于环境,以观察奖励和下一个状态。 然后,采取另一个样本来训练对数概率模型。 第二个样本不一定与第一个样本相同,但是用于训练的奖励来自第一个采样动作,这可能会在梯度计算中引入随机误差。 - -# 8\. 总结 - -在本章中,我们介绍了策略梯度方法。 从策略梯度定理开始,我们制定了四种方法来训练策略网络。 详细讨论了四种方法:REINFORCE,带有基线的 REINFORCE,演员评论家和 A2C 算法。 我们探讨了如何在 Keras 中实现这四种方法。 然后,我们通过检查智能体成功达到目标的次数以及每集获得的总奖励来验证算法。 - -与上一章中讨论的深度 Q 网络[2]相似,基本策略梯度算法可以进行一些改进。 例如,最突出的一个是 A3C [3],它是 A2C 的多线程版本。 这使智能体可以同时接触不同的经验,并异步优化策略和值网络。 但是,在 [OpenAI](https://blog.openai.com/baselines-acktr-a2c/) 进行的实验中,与 A2C 相比,A3C 没有强大的优势,因为前者无法利用当今提供强大的 GPU 的优势。 - -在接下来的两章中,我们将着手于另一个领域-对象检测和语义分割。 对象检测使智能体能够识别和定位给定图像中的对象。 语义分割基于对象类别识别给定图像中的像素区域。 - -# 9\. 参考 - -1. `Richard Sutton and Andrew Barto: Reinforcement Learning: An Introduction: http://incompleteideas.net/book/bookdraft2017nov5.pdf (2017)` -1. `Volodymyr Mnih et al.: Human-level control through deep reinforcement learning, Nature 518.7540 (2015): 529` -1. `Volodymyr Mnih et al.: Asynchronous Methods for Deep Reinforcement Learning, International conference on machine learning, 2016` -1. `Ronald Williams: Simple statistical gradient-following algorithms for connectionist reinforcement learning, Machine learning 8.3-4 (1992): 229-256` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/11.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/11.md deleted file mode 100644 index 73ee0810..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/11.md +++ /dev/null @@ -1,1672 +0,0 @@ -# 十一、对象检测 - -目标检测是计算机视觉最重要的应用之一。 对象检测是同时定位和识别图像中存在的对象的任务。 为了使自动驾驶汽车安全地在街道上行驶,该算法必须检测到行人,道路,车辆,交通信号灯,标志和意外障碍物的存在。 在安全方面,入侵者的存在可以用来触发警报或通知适当的当局。 - -尽管很重要,但是对象检测一直是计算机视觉中的一个长期存在的问题。 已经提出了许多算法,但是通常很慢,并且精度和召回率很低。 与 AlexNet [1]在 ImageNet 大规模图像分类问题中所取得的成就类似,深度学习显着提高了对象检测领域。 最新的对象检测方法现在可以实时运行,并且具有更高的精度和召回率。 - -在本章中,我们重点介绍实时对象检测。 特别是,我们讨论了`tf.keras`中**单发检测**(**SSD**)[2]的概念和实现。 与其他深度学习检测算法相比,SSD 可在现代 GPU 上实现实时检测速度,而表现不会显着下降。 SSD 还易于端到端训练。 - -总之,本章的目的是介绍: - -* 对象检测的概念 -* 多尺度目标检测的概念 -* SSD 作为多尺度目标检测算法 -* `tf.keras`中 SSD 的实现 - -我们将从介绍对象检测的概念开始。 - -# 1\. 对象检测 - -在对象检测中,目标是在图像中定位和识别物体。“图 11.1.1”显示了目标**汽水罐**的目标物检测。 本地化意味着必须估计对象的边界框。 使用左上角像素坐标和右下角像素坐标是用于描述边界框的通用约定。 在“图 11.1.1”中,左上角像素具有坐标`(x_min, y_min)`,而右下角像素的坐标为`(x_max, y_max)`。像素坐标系的原点`(0, 0)`位于整个图像的左上角像素。 - -在执行定位时,检测还必须识别对象。 识别是计算机视觉中的经典识别或分类任务。 至少,对象检测必须确定边界框是属于已知对象还是背景。 可以训练对象检测网络以仅检测一个特定对象,例如“图 11.1.1”中的**汽水罐**。 其他所有内容均视为背景,因此无需显示其边界框。 同一对象的多个实例,例如两个或多个**汽水罐**,也可以通过同一网络检测到,如图“图 11.1.2”所示。 - -![](img/B14853_11_01.png) - -图 11.1.1 说明了对象检测是在图像中定位和识别对象的过程。 - -![](img/B14853_11_02.png) - -图 11.1.2 被训练为检测一个对象实例的同一网络可以检测到同一对象的多个实例。 - -如果场景中存在多个对象,例如在“图 11.1.3”中,则对象检测方法只能识别在其上训练的一个对象。 其他两个对象将被分类为背景,并且不会分配边界框。 - -![](img/B14853_11_03.png) - -图 11.1.3 如果仅在检测汽水罐方面训练了对象检测,它将忽略图像中的其他两个对象。 - -但是,如果重新训练了网络以检测三个对象:1)**汽水罐**,2)**果汁罐**和 3)**水瓶**会同时定位和识别,如图“图 11.1.4”所示。 - -![](img/B14853_11_04.png) - -图 11.1.4 即使背景杂乱或照明发生变化,也可以重新训练对象检测网络以检测所有三个对象。 - -一个好的对象检测器必须在现实环境中具有鲁棒性。“图 11.1.4”显示了一个好的对象检测网络,即使背景杂乱甚至在弱光条件下,也可以定位和识别已知对象。 对象检测器必须具有鲁棒性的其他因素是物体变换(旋转和/或平移),表面反射,纹理变化和噪声。 - -总之,对象检测的目标是针对图像中每个**可识别的**对象同时预测以下内容: - -* `y_cls`或单热向量形式的类别或类 -* `y_box = ((x_min, y_min), (x_max, y_max))`或像素坐标形式的边界框坐标 - -通过解释了对象检测的基本概念,我们可以开始讨论对象检测的某些特定机制。 我们将从介绍锚框开始。 - -# 2\. 锚框 - -从上一节的讨论中,我们了解到,对象检测必须预测边界框区域以及其中的对象类别。 假设与此同时,我们的重点是边界框坐标估计。 - -网络如何预测坐标`(x_min, y_min)`和`(x_max, y_max)`? 网络可以做出与图像的左上角像素坐标和右下角像素坐标相对应的初始猜测,例如`(0, 0)`和`(w, h)`。`w`是图像宽度,而`h`是图像高度。 然后,网络通过对地面真实边界框坐标执行回归来迭代地校正估计。 - -由于可能的像素值存在较大差异,因此使用原始像素估计边界框坐标不是最佳方法。 SSD 代替原始像素,将地面真值边界框和预测边界框坐标之间的像素误差值最小化。 对于此示例,像素的误差值为`(x_min, y_min)`和`(x_max - w, y_max - h)`。 这些值称为`offsets`。 - -为了帮助网络找出正确的边界框坐标,将图像划分为多个区域。 每个区域称为**定位框**。 然后,网络估计每个锚框的**偏移**。 这样得出的预测更接近于基本事实。 - -例如,如图“图 11.2.1”所示,将普通图像尺寸`640 x 480`分为`2 x 1`个区域,从而产生两个锚框。 与`2 x 2`的大小不同,`2 x 1`的划分创建了近似方形的锚框。 在第一个锚点框中,新的偏移量是`(x_min, y_min)`和`{x_max - w/2, y_max - h}`,它们比没有锚框的像素误差值更小。 第二个锚框的偏移量也较小。 - -在“图 11.2.2”中,图像被进一步分割。 这次,锚框为`3 x 2`。第二个锚框偏移为`{x_min - w/3, y_min}`和`{x_max - 2w/3, y_max - h/2}`,这是迄今为止最小的。 但是,如果将图像进一步分为`5 x 4`,则偏移量开始再次增加。 主要思想是,在创建各种尺寸的区域的过程中,将出现最接近地面真值边界框的最佳锚框大小。 使用多尺度锚框有效地检测不同大小的对象将巩固**多尺度对象检测**算法的概念。 - -找到一个最佳的锚框并不是零成本。 尤其是,有些外部锚框的偏移量比使用整个图像还要差。 在这种情况下,SSD 建议这些锚定框不应对整个优化过程有所帮助,而应予以抑制。 在以下各节中,将更详细地讨论排除非最佳锚框的算法。 - -到目前为止,我们已经有三套锚框。 - -第一个创建一个`2 x 1`的锚框网格,每个锚框的尺寸为`(w/2, h)`。 - -第二个创建一个`3 x 2`的锚框网格,每个锚框的尺寸为`(w/3, h/2)`。 - -第三个创建一个`5 x 4`的锚框网格,每个锚框的尺寸为`(w/5, h/4)`。 - -我们还需要多少套锚盒? 它取决于图像的尺寸和对象最小边框的尺寸。 对于此示例中使用的`640 x 480`图像,其他锚点框为: - -`10 x 8`格的锚框,每个框的尺寸为`(w/10, h/8)` - -`20 x 15`格的锚框,每个锚框的尺寸为`(w/20, h/15)` - -`40 x 30`格的锚框,每个框的尺寸为`(w/40, h/30)` - -对于具有`40 x 30`网格的锚框的`640 x 480`图像,最小的锚框覆盖输入图像的`16 x 16`像素斑块,也称为**接收域**。 到目前为止,包围盒的总数为 1608。对于所有尺寸,最小的**缩放因子**可以总结为: - -![](img/B14853_11_010.png) (Equation 11.2.1) - -锚框如何进一步改进? 如果我们允许锚框具有不同的纵横比,则可以减少偏移量。 每个调整大小的锚点框的质心与原始锚点框相同。 除宽高比 1 外,SSD [2]包括其他宽高比: - -![](img/B14853_11_011.png) (Equation 11.2.2) - -对于每个纵横比`a[i]`,对应的锚框尺寸为: - -![](img/B14853_11_013.png) (Equation 11.2.3) - -`(s[xj], s[yj])`是“公式 11.2.1”中的第`j`个比例因子。 - -使用每个锚框五个不同的长宽比,锚框的总数将增加到`1,608 x 5 = 8,040`。“图 11.2.3”显示了`(s[x4], s[y4]) = (1/3, 1/2)`和`a[i ∈ {0, 1, 3}] = 1, 2, 1/2`情况下的锚框。 - -请注意,为了达到一定的纵横比,我们不会使锚框变形。 而是调整锚框的宽度和高度。 - -对于`a[0] = 1`,SSD 建议使用其他尺寸的锚框: - -![](img/B14853_11_018.png) (Equation 11.2.4) - -现在每个区域有六个锚定框。 有五个是由于五个纵横比,另外还有一个纵横比为 1。新的锚框总数增加到 9,648。 - -![](img/B14853_11_05.png) - -图 11.2.1 将图像划分为多个区域(也称为锚框),使网络可以进行更接近地面真实情况的预测。 - -![](img/B14853_11_06.png) - -图 11.2.2 使用较小的锚框可以进一步减少偏移。 - -![](img/B14853_11_07.png) - -图 11.2.3 具有比例因子`(s[x4], s[y4]) = (1/3, 1/2)`和纵横比`a[i ∈ {0, 1, 3}] = 1, 2, 1/2`的一个区域的锚框。 - -下面的“列表 11.2.1”显示了锚框生成函数`anchor_boxes()`。 给定输入的图像形状(`image_shape`),纵横比(`aspect_ratios`)和缩放因子(`sizes`),将计算不同的锚框大小并将其存储在名为`width_height`的列表中。 从给定的特征映射形状(`feature_shape`或`(h_fmap, w_fmap)`和`width_height`, 生成具有尺寸`(h_fmap, w_fmap, n_boxes, 4)`。`n_boxes`或每个特征映射点的锚点框数是基于纵横比和等于 1 的纵横比的一个附加大小计算的。 - -“列表 11.2.1”:锚框生成函数的`layer_utils.py`函数: - -```py -def anchor_boxes(feature_shape, - image_shape, - index=0, - n_layers=4, - aspect_ratios=(1, 2, 0.5)): - """ Compute the anchor boxes for a given feature map. - Anchor boxes are in minmax format -``` - -```py - Arguments: - feature_shape (list): Feature map shape - image_shape (list): Image size shape - index (int): Indicates which of ssd head layers - are we referring to - n_layers (int): Number of ssd head layers -``` - -```py - Returns: - boxes (tensor): Anchor boxes per feature map - """ -``` - -```py - # anchor box sizes given an index of layer in ssd head - sizes = anchor_sizes(n_layers)[index] - # number of anchor boxes per feature map pt - n_boxes = len(aspect_ratios) + 1 - # ignore number of channels (last) - image_height, image_width, _ = image_shape - # ignore number of feature maps (last) - feature_height, feature_width, _ = feature_shape -``` - -```py - # normalized width and height - # sizes[0] is scale size, sizes[1] is sqrt(scale*(scale+1)) - norm_height = image_height * sizes[0] - norm_width = image_width * sizes[0] -``` - -```py - # list of anchor boxes (width, height) - width_height = [] - # anchor box by aspect ratio on resized image dims - # Equation 11.2.3 - for ar in aspect_ratios: - box_width = norm_width * np.sqrt(ar) - box_height = norm_height / np.sqrt(ar) - width_height.append((box_width, box_height)) - # multiply anchor box dim by size[1] for aspect_ratio = 1 - # Equation 11.2.4 - box_width = image_width * sizes[1] - box_height = image_height * sizes[1] - width_height.append((box_width, box_height)) -``` - -```py - # now an array of (width, height) - width_height = np.array(width_height) -``` - -```py - # dimensions of each receptive field in pixels - grid_width = image_width / feature_width - grid_height = image_height / feature_height -``` - -```py - # compute center of receptive field per feature pt - # (cx, cy) format - # starting at midpoint of 1st receptive field - start = grid_width * 0.5 - # ending at midpoint of last receptive field - end = (feature_width - 0.5) * grid_width - cx = np.linspace(start, end, feature_width) -``` - -```py - start = grid_height * 0.5 - end = (feature_height - 0.5) * grid_height - cy = np.linspace(start, end, feature_height) -``` - -```py - # grid of box centers - cx_grid, cy_grid = np.meshgrid(cx, cy) -``` - -```py - # for np.tile() - cx_grid = np.expand_dims(cx_grid, -1) - cy_grid = np.expand_dims(cy_grid, -1) -``` - -```py - # tensor = (feature_map_height, feature_map_width, n_boxes, 4) - # aligned with image tensor (height, width, channels) - # last dimension = (cx, cy, w, h) - boxes = np.zeros((feature_height, feature_width, n_boxes, 4)) -``` - -```py - # (cx, cy) - boxes[..., 0] = np.tile(cx_grid, (1, 1, n_boxes)) - boxes[..., 1] = np.tile(cy_grid, (1, 1, n_boxes)) -``` - -```py - # (w, h) - boxes[..., 2] = width_height[:, 0] - boxes[..., 3] = width_height[:, 1] -``` - -```py - # convert (cx, cy, w, h) to (xmin, xmax, ymin, ymax) - # prepend one dimension to boxes - # to account for the batch size = 1 - boxes = centroid2minmax(boxes) - boxes = np.expand_dims(boxes, axis=0) - return boxes -``` - -```py -def centroid2minmax(boxes): - """Centroid to minmax format - (cx, cy, w, h) to (xmin, xmax, ymin, ymax) -``` - -```py - Arguments: - boxes (tensor): Batch of boxes in centroid format -``` - -```py - Returns: - minmax (tensor): Batch of boxes in minmax format - """ - minmax= np.copy(boxes).astype(np.float) - minmax[..., 0] = boxes[..., 0] - (0.5 * boxes[..., 2]) - minmax[..., 1] = boxes[..., 0] + (0.5 * boxes[..., 2]) - minmax[..., 2] = boxes[..., 1] - (0.5 * boxes[..., 3]) - minmax[..., 3] = boxes[..., 1] + (0.5 * boxes[..., 3]) - return minmax -``` - -我们已经介绍了锚框如何协助对象检测以及如何生成它们。 在下一节中,我们将介绍一种特殊的锚点框:真实情况锚点框。 给定图像中的对象,必须将其分配给多个锚点框之一。 这就是,称为真实情况锚定框。 - -# 3\. 真实情况锚框 - -从“图 11.2.3”看来,给定一个对象边界框,有许多可以分配给对象的真实情况锚定框。 实际上,仅出于“图 11.2.3”中的说明,已经有 3 个锚定框。 如果考虑每个区域的所有锚框,则仅针对`(s[x4], s[y4]) = (1/3, 1/2)`就有`6 x 6 = 36`个地面真实框。 使用所有 9,648 个锚点框显然过多。 所有锚定框中只有一个应与地面真值边界框相关联。 所有其他锚点框都是背景锚点框。 选择哪个对象应被视为图像中对象的真实情况锚定框的标准是什么? - -选择锚框的基础称为**交并比**(**IoU**)。 IoU 也称为 *Jaccard 指数*。 在“图 11.3.1”中说明了 IoU。 给定 2 个区域,对象边界框`B[0]`和锚定框`A[1]`,IoU 等于重叠除以合并区域的面积: - -![](img/B14853_11_022.png) (Equation 11.3.1) - -![](img/B14853_11_08.png) - -图 11.3.1 IoU 等于(左)候选锚点框`A[1]`与(右)对象边界框`B[0]`之间的相交面积除以并集面积。 - -我们删除了该等式的下标。 对于给定的对象边界框`B[i]`,对于所有锚点框`A[j]`,地面真值锚点框`A[j(gt)]`是具有最大 IoU 的一个: - -![](img/B14853_11_023.png) (Equation 11.3.2) - -请注意,对于每个对象,只有一个基于“公式 11.3.2”的地面真值锚定框。 此外,必须在所有比例因子和尺寸(长宽比和附加尺寸)中对所有锚框进行最大化。 在“图 11.3.1”中,在 9,648 个锚框中仅显示了一个比例因子大小。 - -为了说明“公式 11.3.2”,假设考虑了“图 11.3.1”中纵横比为 1 的锚框。 对于每个锚框,估计的 IoU 均显示在“表 11.3.1”中。 由于边界框`B[0]`的最大 IoU 为 0.32,因此带有锚框`A[1]`,`A[1]`被分配为地面真值边界框`B[0]`。`A[1]`也被称为**正锚框**。 - -正锚定框的类别和偏移量是相对于其地面真值边界框确定的。 正锚定框的类别与其地面真值边界框相同。 同时,可以将正锚框偏移量计算为等于地面真实边界框坐标减去其自身的边界框坐标。 - -其余锚框发生了什么,`A[0]`,`A[2]`,`A[3]`,`A[4]`,和`A[5]`? 我们可以通过找到他们的 IoU 大于某个阈值的边界框来给他们第二次机会。 - -例如,如果阈值为 0.5,则没有可分配给它们的地面真理边界框。 如果阈值降低到 0.25,则`A[4]`也分配有地面真值边界框`B[0]`,因为其 IoU 为 0.30 。 将`A[4]`添加到肯定锚框列表中。 在这本书中,`A[4]`被称为额外的正面锚盒。 没有地面边界框的其余锚框称为**负锚框**。 - -在以下有关损失函数的部分中,负锚框不构成偏移损失函数。 - -| |`B[0]`| -| --- | --- | -|`A[0]`| 0 | -|`A[1]`| 0.32 | -|`A[2]`| 0 | -|`A[3]`| 0 | -|`A[4]`| 0.30 | -|`A[5]`| 0 | - -“表 11.3.1”每个锚框`A[j ∈ 0 .. 5]`的 IoU,带有对象边界框`B[0]`,如“图 11.3.1”所示。 - -如果加载了另一个带有 2 个要检测的对象的图像,我们将寻找 2 个正 IoU,最大 IoU,并带有边界框`B[0]`和`B[1]`。 然后,我们使用边界框`B[0]`和`B[1]`寻找满足最小 IoU 准则的额外正锚框。 - -为了简化讨论,我们只考虑每个区域一个锚框。 实际上,应该考虑代表不同缩放比例,大小和纵横比的所有锚框。 在下一节中,我们讨论如何制定损失函数,这些损失函数将通过 SSD 网络进行优化。 - -“列表 11.3.1”显示了`get_gt_data()`的实现,该实现计算锚定框的真实情况标签。 - -“列表 11.3.1”:`layer_utils.py` - -```py -def get_gt_data(iou, - n_classes=4, - anchors=None, - labels=None, - normalize=False, - threshold=0.6): - """Retrieve ground truth class, bbox offset, and mask - - Arguments: - iou (tensor): IoU of each bounding box wrt each anchor box - n_classes (int): Number of object classes - anchors (tensor): Anchor boxes per feature layer - labels (list): Ground truth labels - normalize (bool): If normalization should be applied - threshold (float): If less than 1.0, anchor boxes>threshold - are also part of positive anchor boxes -``` - -```py - Returns: - gt_class, gt_offset, gt_mask (tensor): Ground truth classes, - offsets, and masks - """ - # each maxiou_per_get is index of anchor w/ max iou - # for the given ground truth bounding box - maxiou_per_gt = np.argmax(iou, axis=0) -``` - -```py - # get extra anchor boxes based on IoU - if threshold < 1.0: - iou_gt_thresh = np.argwhere(iou>threshold) - if iou_gt_thresh.size > 0: - extra_anchors = iou_gt_thresh[:,0] - extra_classes = iou_gt_thresh[:,1] - extra_labels = labels[extra_classes] - indexes = [maxiou_per_gt, extra_anchors] - maxiou_per_gt = np.concatenate(indexes, - axis=0) - labels = np.concatenate([labels, extra_labels], - axis=0) -``` - -```py - # mask generation - gt_mask = np.zeros((iou.shape[0], 4)) - # only indexes maxiou_per_gt are valid bounding boxes - gt_mask[maxiou_per_gt] = 1.0 -``` - -```py - # class generation - gt_class = np.zeros((iou.shape[0], n_classes)) - # by default all are background (index 0) - gt_class[:, 0] = 1 - # but those that belong to maxiou_per_gt are not - gt_class[maxiou_per_gt, 0] = 0 - # we have to find those column indexes (classes) - maxiou_col = np.reshape(maxiou_per_gt, - (maxiou_per_gt.shape[0], 1)) - label_col = np.reshape(labels[:,4], - (labels.shape[0], 1)).astype(int) - row_col = np.append(maxiou_col, label_col, axis=1) - # the label of object in maxio_per_gt - gt_class[row_col[:,0], row_col[:,1]] = 1.0 -``` - -```py - # offsets generation - gt_offset = np.zeros((iou.shape[0], 4)) -``` - -```py - #(cx, cy, w, h) format - if normalize: - anchors = minmax2centroid(anchors) - labels = minmax2centroid(labels) - # bbox = bounding box - # ((bbox xcenter - anchor box xcenter)/anchor box width)/.1 - # ((bbox ycenter - anchor box ycenter)/anchor box height)/.1 - # Equation 11.4.8 Chapter 11 - offsets1 = labels[:, 0:2] - anchors[maxiou_per_gt, 0:2] - offsets1 /= anchors[maxiou_per_gt, 2:4] - offsets1 /= 0.1 -``` - -```py - # log(bbox width / anchor box width) / 0.2 - # log(bbox height / anchor box height) / 0.2 - # Equation 11.4.8 Chapter 11 - offsets2 = np.log(labels[:, 2:4]/anchors[maxiou_per_gt, 2:4]) - offsets2 /= 0.2 -``` - -```py - offsets = np.concatenate([offsets1, offsets2], axis=-1) -``` - -```py - # (xmin, xmax, ymin, ymax) format - else: - offsets = labels[:, 0:4] - anchors[maxiou_per_gt] -``` - -```py - gt_offset[maxiou_per_gt] = offsets -``` - -```py - return gt_class, gt_offset, gt_mask -``` - -```py -def minmax2centroid(boxes): - """Minmax to centroid format - (xmin, xmax, ymin, ymax) to (cx, cy, w, h) -``` - -```py - Arguments: - boxes (tensor): Batch of boxes in minmax format -``` - -```py - Returns: - centroid (tensor): Batch of boxes in centroid format - """ - centroid = np.copy(boxes).astype(np.float) - centroid[..., 0] = 0.5 * (boxes[..., 1] - boxes[..., 0]) - centroid[..., 0] += boxes[..., 0] - centroid[..., 1] = 0.5 * (boxes[..., 3] - boxes[..., 2]) - centroid[..., 1] += boxes[..., 2] - centroid[..., 2] = boxes[..., 1] - boxes[..., 0] - centroid[..., 3] = boxes[..., 3] - boxes[..., 2] - return centroid -``` - -`maxiou_per_gt = np.argmax(iou, axis=0)`实现了“公式 11.3.2”。 额外的阳性锚框是基于由`iou_gt_thresh = np.argwhere(iou>threshold)`实现的用户定义的阈值确定的。 - -仅当阈值小于 1.0 时,才会查找额外的正锚框。 所有带有地面真值边界框的锚框(即组合的正锚框和额外的正锚框)的索引成为真实情况掩码的基础: - -`gt_mask[maxiou_per_gt] = 1.0`。 - -所有其他锚定框(负锚定框)的掩码为 0.0,并且不影响偏移损失函数的优化。 - -每个锚定框的类别`gt_class`被分配为其地面实况边界框的类别。 最初,为所有锚框分配背景类: - -```py - # class generation - gt_class = np.zeros((iou.shape[0], n_classes)) - # by default all are background (index 0) - gt_class[:, 0] = 1 -``` - -然后,将每个正面锚点框的类分配给其非背景对象类: - -```py - # but those that belong to maxiou_per_gt are not - gt_class[maxiou_per_gt, 0] = 0 - # we have to find those column indexes (classes) - maxiou_col = np.reshape(maxiou_per_gt, - (maxiou_per_gt.shape[0], 1)) - label_col = np.reshape(labels[:,4], - (labels.shape[0], 1)).astype(int) - row_col = np.append(maxiou_col, label_col, axis=1) - # the label of object in maxio_per_gt - gt_class[row_col[:,0], row_col[:,1]] = 1.0 -``` - -`row_col[:,0]`是正锚框的索引,而`row_col[:,1]`是它们的非背景对象类的索引。 请注意,`gt_class`是单热点向量的数组。 这些值都为零,除了锚点框对象的索引处。 索引 0 是背景,索引 1 是第一个非背景对象,依此类推。 最后一个非背景对象的索引等于`n_classes-1`。 - -例如,如果锚点框 0 是负锚点框,并且有 4 个对象类别(包括背景),则: - -```py -gt_class[0] = [1.0, 0.0, 0.0, 0.0] -``` - -如果锚定框 1 是正锚定框,并且其地面真值边界框包含带有标签 2 的**汽水罐**,则: - -```py -gt_class[1] = [0.0, 0.0, 1.0, 0.0] -``` - -最后,偏移量只是地面真实边界框坐标减去锚框坐标: - -```py - # (xmin, xmax, ymin, ymax) format - else: - offsets = labels[:, 0:4] - anchors[maxiou_per_gt] -``` - -注意,我们仅计算正锚框的偏移量。 - -如果选择了该选项,则可以将偏移量标准化。 下一部分将讨论偏移量归一化。 我们将看到: - -```py - #(cx, cy, w, h) format - if normalize: -``` - -```py - anchors = minmax2centroid(anchors) - labels = minmax2centroid(labels) - # bbox = bounding box - # ((bbox xcenter - anchor box xcenter)/anchor box width)/.1 - # ((bbox ycenter - anchor box ycenter)/anchor box height)/.1 - # Equation 11.4.8 - offsets1 = labels[:, 0:2] - anchors[maxiou_per_gt, 0:2] - offsets1 /= anchors[maxiou_per_gt, 2:4] - offsets1 /= 0.1 -``` - -```py - # log(bbox width / anchor box width) / 0.2 - # log(bbox height / anchor box height) / 0.2 - # Equation 11.4.8 - offsets2 = np.log(labels[:, 2:4]/anchors[maxiou_per_gt, 2:4]) - offsets2 /= 0.2 -``` - -```py - offsets = np.concatenate([offsets1, offsets2], axis=-1) -``` - -只是“公式 11.4.8”的实现,下一节将进行讨论,为方便起见,在此处显示: - -![](img/B14853_11_025.png) (Equation 11.4.8) - -现在我们已经了解了地面真锚框的作用,我们将继续研究对象检测中的另一个关键组件:损失函数。 - -# 4\. 损失函数 - -在 SSD 中,有数千个锚定框。 如本章前面所述,对象检测的目的是预测每个锚框的类别和偏移量。 我们可以对每个预测使用以下损失函数: - -* `L_cls` - `y_cls`的分类交叉熵损失 -* `L_off` - L1 或 L2,用于`y_cls`。 请注意,只有正锚框有助于`L_off` L1,也称为**平均绝对误差**(**MAE**)损失,而 L2 也称为**均方误差**(**MSE**)损失。 - -总的损失函数为: - -![](img/B14853_11_081.png) (Equation 11.4.1) - -对于每个定位框,网络都会预测以下内容: - -* `y_cls`或单热向量形式的类别或类 -* `y_off = ((x_omin, y_omin), (x_omax, y_omax))`或相对于锚框的像素坐标形式的偏移。 - -为了方便计算,可以将偏移量更好地表示为以下形式: - -`y_off = ((x_omin, y_omin), (x_omax, y_omax))` (Equation 11.4.2) - -SSD 是一种监督对象检测算法。 可以使用以下基本真值: - -* `y_label`或要检测的每个对象的类标签 -* `y_gt = (x_gmin, x_gmax, y_gmin, y_gmax)`或地面真实偏差,其计算公式如下: - -`y_gt = (x_bmin – x_amin, x_bmax – x_amax, y_bmin – y_amin, y_bmax – y_amax)` (Equation 11.4.3) - -换句话说,将地面真实偏移量计算为对象包围盒相对于锚定框的地面真实偏移量。 为了清楚起见,`y_box`下标中的细微调整。 如上一节所述,基本真值是通过`get_gt_data()`函数计算的。 - -但是,SSD 不建议直接从预测原始像素误差值`y_off`。 而是使用归一化的偏移值。 地面真值边界框和锚点框坐标首先以质心尺寸格式表示: - -![](img/B14853_11_026.png) - -![](img/B14853_11_027.png) - -(Equation 11.4.4) - -哪里: - -![](img/B14853_11_028.png) (Equation 11.4.5) - -是边界框中心的坐标,并且: - -`(w[b], h[b]) = (x_max – x_min, y_max - y_min)` (Equation 11.4.6) - -分别对应于宽度和高度。 锚框遵循相同的约定。 归一化的真实情况偏移量表示为: - -![](img/B14853_11_029.png) (Equation 11.4.7) - -通常,`y_gt`的元素值很小,`||y_gt|| << 1.0`。 较小的梯度会使网络训练更加难以收敛。 - -为了缓解该问题,将每个元素除以其估计的标准差。 由此产生的基本事实抵消了: - -![](img/B14853_11_025.png) (Equation 11.4.8) - -推荐值为:`σ[x] = σ[y] = 0.1`和`σ[w] = σ[h] = 0.2`。 换句话说,沿着`x`和`y`轴的像素误差的预期范围是`± 10%`,而对于宽度和高度,则是`± 20%。 这些值纯粹是任意的。 - -“列表 11.4.1”:`loss.py` L1 和平滑 L1 损失函数 - -```py -from tensorflow.keras.losses import Huber -def mask_offset(y_true, y_pred): - """Pre-process ground truth and prediction data""" - # 1st 4 are offsets - offset = y_true[..., 0:4] - # last 4 are mask - mask = y_true[..., 4:8] - # pred is actually duplicated for alignment - # either we get the 1st or last 4 offset pred - # and apply the mask - pred = y_pred[..., 0:4] - offset *= mask - pred *= mask - return offset, pred - -def l1_loss(y_true, y_pred): - """MAE or L1 loss - """ - offset, pred = mask_offset(y_true, y_pred) - # we can use L1 - return K.mean(K.abs(pred - offset), axis=-1) - -def smooth_l1_loss(y_true, y_pred): - """Smooth L1 loss using tensorflow Huber loss - """ - offset, pred = mask_offset(y_true, y_pred) - # Huber loss as approx of smooth L1 - return Huber()(offset, pred) -``` - -此外,代替`y_cls`的 L1 损失,SSD 受 Fast-RCNN [3]启发,使用平滑 L1: - -![](img/B14853_11_035.png) (Equation 11.4.9) - -其中`u`代表地面真实情况与预测之间的误差中的每个元素: - -![](img/B14853_11_037.png) (Equation 11.4.10) - -与 L1 相比,平滑 L1 更健壮,并且对异常值的敏感性较低。 在 SSD 中,`σ = 1`。 作为`σ -> ∞`,平滑 L1 接近 L1。 L1 和平滑 L1 损失函数都在“列表 11.4.1”中显示。 `mask_offset()`方法可确保仅在具有地面真实边界框的预测上计算偏移量。 平滑的 L1 函数与`σ = 1`[8]时的 Huber 损失相同。 - -作为对损失函数的进一步改进,RetinaNet [3]建议将 CE`y_cls`的分类交叉熵函数替换为焦点损失 FL: - -![](img/B14853_11_041.png) (Equation 11.4.11) - -![](img/B14853_11_042.png) (Equation 11.4.12) - -区别在于额外因素`α(1 - p[i])^γ`。 在 RetinaNet 中,当`γ = 2`和`α = 0.25`时,对象检测效果最好。 焦点损失在“列表 11.4.2”中实现。 - -“列表 11.4.2”:`loss.py`焦点损失 - -```py -def focal_loss_categorical(y_true, y_pred): - """Categorical cross-entropy focal loss""" - gamma = 2.0 - alpha = 0.25 -``` - -```py - # scale to ensure sum of prob is 1.0 - y_pred /= K.sum(y_pred, axis=-1, keepdims=True) -``` - -```py - # clip the prediction value to prevent NaN and Inf - epsilon = K.epsilon() - y_pred = K.clip(y_pred, epsilon, 1\. - epsilon) - # calculate cross entropy - cross_entropy = -y_true * K.log(y_pred) -``` - -```py - # calculate focal loss - weight = alpha * K.pow(1 - y_pred, gamma) - cross_entropy *= weight -``` - -```py - return K.sum(cross_entropy, axis=-1) -``` - -聚焦损失的动机是,如果我们检查图像,则大多数锚框应分类为背景或负锚框。 只有很少的正锚框是代表目标对象的良好候选对象。 负熵损失是造成交叉熵损失的主要因素。 因此,负锚框的贡献使优化过程中正锚框的贡献无法实现。 这种现象也称为**类不平衡**,其中一个或几个类占主导地位。 有关其他详细信息,Lin 等。 文献[4]讨论了对象检测中的类不平衡问题。 - -有了**焦点损失**,我们在优化过程的早期就确信负锚框属于背景。 因此,由于`p[i] -> 1.0`,项`(1 - p[i])^γ`减少了负锚框的贡献。 对于正锚框,其贡献仍然很大,因为`p[i]`远非 1.0。 - -既然我们已经讨论了锚定框,地面真值锚定框和损失函数的概念,我们现在准备介绍实现多尺度目标检测算法的 SSD 模型架构。 - -# 5\. SSD 模型架构 - -“图 11.5.1”显示了 SSD 的模型架构,该模型实现了多尺度单发目标检测的概念框架。 网络接受 RGB 图像,并输出几个预测级别。 基本或**骨干**网络提取用于分类和偏移量预测的下游任务的特征。 ResNet50 是骨干网络的一个很好的例子,它类似于“第 2 章”,“深度神经网络”中讨论,实现和评估的内容。 在骨干网络之后,对象检测任务由执行其余的网络,我们将其称为 **SSD 头**。 - -骨干网络可以是具有冻结权重的预训练网络(例如,以前为 ImageNet 分类而训练),也可以是与对象检测一起训练的网络。 如果使用预先训练的基础网络,则可以利用重用以前从大型数据集中学习的特征提取过滤器的优势。 此外,由于冻结了骨干网参数,因此可以加快学习速度。 仅训练对象检测中的顶层。 在这本书中,骨干网是与对象检测联合训练的,因为我们假设我们不一定需要访问预先训练的骨干网。 - -骨干网网络通常使用跨步 2 或通过最大池化实现几轮下采样。 对于 ResNet50,这是 4 倍。 基本网络变为`(w/2^4, h/2^4) = (w/16, h/16)`之后,特征映射的结果尺寸。 如果图像的宽度和高度均可以被 16 整除,则尺寸是精确的。 - -例如,对于`640 x 480`的图像,生成的特征映射的尺寸为`40 x 30 = 1200`。 如前几节所述,这是基础网络之后长宽比等于 1 的锚点框的数量。 此数字乘以每个锚定框的大小数。 在前面的部分中,由于长宽比,有 6 种不同的尺寸,而长宽比为 1 时,还有一个其他尺寸。 - -在本书中,我们将纵横比限制为`a[i ∈ {0, 1, 3}] = 1, 2, 1/2`。 因此,将只有 4 种不同的大小。 对于`640 x 480`图像,第一组锚框的锚框总数为`n[1] = 4,800`。 - -在“图 11.5.1”中,指示密集网格以表明对于第一组预测,存在大量预测(例如:`40 x 30 x 4`),从而导致大量补丁 。 尽管每个锚点框有 4 种尺寸,但为清楚起见,仅显示了与宽高比 1 对应的`16 x 16`锚点框。 - -此锚框也是`40 x 30 x n_filter`特征映射中每个元素的接受字段大小。`n_filter`是骨干网最后卷积层中过滤器的数量。 对于每个锚框,都将预测类别和偏移量。 - -总共有`n[1]`类和`n[1]`偏移量预测。 单热类预测的维数等于要检测的对象类别的数量,背景为 1。 每个偏移量变量预测的尺寸为 4,对应于`(x, y)`到预测边界框的 2 个角的偏移量。 - -类预测器由卷积层组成,该卷积层由使用 *softmax* 进行分类交叉熵损失的激活层终止。 偏移量预测值是具有线性激活的独立卷积层。 - -在基础网络之后可以应用其他特征提取模块。 每个特征提取器块都是`Conv2D(strides=2)-BN-ELU`的形式。 在特征提取块之后,特征映射的大小减半,并且过滤器的数量增加一倍。 例如,基本网络之后的第一个特征提取器块具有`20 x 15 x 2 n_filter`特征映射。 根据该特征映射,使用卷积层进行`n[2]`类和`n[2]`偏移量预测。`n[2] = 20 x 15 x 4 = 1,200` - -可以继续添加具有类和偏移量预测变量的特征提取块的过程。 在前面的部分中,对于`640 x 480`的图像,最大可达`2 x 1 x 2^5 n_filter`特征映射产生`n[6]`类和`n[6]`抵消了其中`n[6] = 2 x 1 x 4 = 8`的预测。 到 6 层特征提取和预测块。 在第 6 个块之后,一个`640 x 480`图像的锚点映射预测总数为 9,648。 - -在前面的部分中,锚定框的比例因子大小按降序排列: - -![](img/B14853_11_050.png) Equation 11.5.1) - -这样做是为了使讨论清晰。 在本节中,应该意识到比例因子的大小实际上是从骨干网之后的特征映射大小开始的。 实际上,缩放因子应按升序排列: - -![](img/B14853_11_051.png) (Equation 11.5.2) - -这意味着如果将特征提取块的数量减少到 4,则缩放因子为: - -![](img/B14853_11_052.png) (Equation 11.5.3) - -如果特征映射的宽度或高度不能被 2 整除(例如:15),则将应用天花板函数(例如:`ceil(15/2) = 8`)。 但是,在原始的 SSD [2]实现中,所使用的缩放因子被简化为`[0.2, 0.9]`范围,该范围通过缩放因子的数量或特征提取块的数量`n_layers`进行线性缩放: - -```py -s = np.linspace(0.2, 0.9, n_layers + 1) -``` - -![](img/B14853_11_09.png) - -图 11.5.1 SSD 模型架构。请注意,对于`w/16 x h/16`网格,锚框的数量可能不准确。 网格显示了锚框的紧密包装。 - -讨论了 SSD 模型架构之后,现在让我们看一下如何在 Keras 中实现 SSD 模型架构。 - -# 6\. Keras 中的 SSD 模型架构 - -与前面章节中的代码示例不同,SSD 的`tf.keras`实现更加复杂。 与 SSD 的其他`tf.keras`实现相比,本章中提供的代码示例重点介绍多尺度目标检测的关键概念。 可以进一步优化代码实现的某些部分,例如缓存地面真锚框类,偏移量和掩码。 在我们的示例中,每次从文件系统加载图像时,线程都会计算出地面真实值。 - -“图 11.6.1”显示了包含 SSD 的`tf.keras`实现的代码块的概述。 `ssd-11.6.1.py`中的 SSD 对象可以构建,训练和评估 SSD 模型。 它借助`model.py`和`resnet.py`以及`data_generator.py`中的多线程数据生成器,位于 SSD 模型创建器的顶部。 SSD 模型实现了“图 11.5.1”中所示的 SSD 架构。 每个主要模块的实现将在后续部分中详细讨论。 - -SSD 模型使用 ResNet 作为其骨干网络。 它在`resnet.py`中调用 ResNet V1 或 V2 模型创建者。 与前几章中的示例不同,SSD 使用的数据集由数千个高分辨率图像组成。 多线程数据生成器将加载文件,并且将这些文件从文件系统排队。 它还计算锚点箱的地面真值标签。 如果没有多线程数据生成器,则在训练期间图像的加载和排队以及地面真值的计算将非常缓慢。 - -有许多小的但重要的例程在后台运行。 这些都集中存储在工具块中。 这些例程创建锚框,计算 IoU,建立真实情况标签,运行非最大抑制,绘制标签和框,在视频帧上显示检测到的对象,提供损失函数等。 - -![](img/B14853_11_10.png) - -图 11.6.1 实现 SSD 的代码块。 - -# 7\. Keras 中的 SSD 对象 - -“列表 11.7.1”(很快显示)显示了 SSD 类。 说明了两个主要例程: - -1. 使用`build_model()`创建 SSD 模型 - -1. 通过`build_generator()`实例化数据生成器 - -`build_model`首先根据训练标签创建数据字典。 字典存储图像文件名以及每个图像中每个对象的地面真实边界框坐标和类。 之后,构建骨干网和 SSD 网络模型。 模型创建的最重要产品是`self.ssd` – SSD 的网络模型。 - -标签存储在 csv 文件中。 对于本书中使用的示例训练图像,标签以以下格式保存在`dataset/drinks/labels_train.csv`中: - -```py -frame,xmin,xmax,ymin,ymax,class_id -0001000.jpg,310,445,104,443,1 -0000999.jpg,194,354,96,478,1 -0000998.jpg,105,383,134,244,1 -0000997.jpg,157,493,89,194,1 -0000996.jpg,51,435,207,347,1 -0000995.jpg,183,536,156,283,1 -0000994.jpg,156,392,178,266,2 -0000993.jpg,207,449,119,213,2 -0000992.jpg,47,348,213,346,2 -… -``` - -“列表 11.7.1”:`ssd-11.6.1.py` - -```py -class SSD: - """Made of an ssd network model and a dataset generator. - SSD defines functions to train and validate - an ssd network model. -``` - -```py - Arguments: - args: User-defined configurations -``` - -```py - Attributes: - ssd (model): SSD network model - train_generator: Multi-threaded data generator for training - """ - def __init__(self, args): - """Copy user-defined configs. - Build backbone and ssd network models. - """ - self.args = args - self.ssd = None - self.train_generator = None - self.build_model() -``` - -```py - def build_model(self): - """Build backbone and SSD models.""" - # store in a dictionary the list of image files and labels - self.build_dictionary() - # input shape is (480, 640, 3) by default - self.input_shape = (self.args.height, - self.args.width, - self.args.channels) -``` - -```py - # build the backbone network (eg ResNet50) - # the number of feature layers is equal to n_layers - # feature layers are inputs to SSD network heads - # for class and offsets predictions - self.backbone = self.args.backbone(self.input_shape, - n_layers=self.args.layers) -``` - -```py - # using the backbone, build ssd network - # outputs of ssd are class and offsets predictions - anchors, features, ssd = build_ssd(self.input_shape, - self.backbone, - n_layers=self.args.layers, - n_classes=self.n_classes) - # n_anchors = num of anchors per feature point (eg 4) - self.n_anchors = anchors - # feature_shapes is a list of feature map shapes - # per output layer - used for computing anchor boxes sizes - self.feature_shapes = features - # ssd network model - self.ssd = ssd -``` - -```py - def build_dictionary(self): - """Read input image filenames and obj detection labels - from a csv file and store in a dictionary. - """ - # train dataset path - path = os.path.join(self.args.data_path, - self.args.train_labels) -``` - -```py - # build dictionary: - # key=image filaname, value=box coords + class label - # self.classes is a list of class labels - self.dictionary, self.classes = build_label_dictionary(path) - self.n_classes = len(self.classes) - self.keys = np.array(list(self.dictionary.keys())) -``` - -```py - def build_generator(self): - """Build a multi-thread train data generator.""" -``` - -```py - self.train_generator = \ - DataGenerator(args=self.args, - dictionary=self.dictionary, - n_classes=self.n_classes, - feature_shapes=self.feature_shapes, - n_anchors=self.n_anchors, - shuffle=True) -``` - -“列表 11.7.2”显示了 SSD 对象中的另一种重要方法`train()`。 指示了使用默认损失函数或改进的损失函数的选项,如先前部分所述。 还有一个选项可以选择仅平滑 L1。 - -`self.ssd.fit_generator()`是此函数中最重要的调用。 它借助多线程数据生成器启动有监督的训练。 在每个周期,都会执行两个回调函数。 首先,将模型权重保存到文件中。 然后,对于 ResNet 模型,以与“第 2 章”,“深度神经网络”相同的方式使用的改进的学习率调度器称为: - -“列表 11.7.2”:`ssd-11.6.1.py` - -```py - def train(self): - """Train an ssd network.""" - # build the train data generator - if self.train_generator is None: - self.build_generator() -``` - -```py - optimizer = Adam(lr=1e-3) - # choice of loss functions via args - if self.args.improved_loss: - print_log("Focal loss and smooth L1", self.args.verbose) - loss = [focal_loss_categorical, smooth_l1_loss] - elif self.args.smooth_l1: - print_log("Smooth L1", self.args.verbose) - loss = ['categorical_crossentropy', smooth_l1_loss] - else: - print_log("Cross-entropy and L1", self.args.verbose) - loss = ['categorical_crossentropy', l1_loss] -``` - -```py - self.ssd.compile(optimizer=optimizer, loss=loss) -``` - -```py - # prepare callbacks for saving model weights - # and learning rate scheduler - # learning rate decreases by 50% every 20 epochs - # after 60th epoch - checkpoint = ModelCheckpoint(filepath=filepath, - verbose=1, - save_weights_only=True) - scheduler = LearningRateScheduler(lr_scheduler) -``` - -```py - callbacks = [checkpoint, scheduler] - # train the ssd network - self.ssd.fit_generator(generator=self.train_generator, - use_multiprocessing=True, - callbacks=callbacks, - epochs=self.args.epochs, - workers=self.args.workers) -``` - -在下一部分中,我们将讨论 Keras 中 SSD 架构实现的其他详细信息。 特别是 SSD 模型和多线程数据生成器的实现。 - -# 8\. Keras 中的 SSD 模型 - -“列表 11.8.1”显示了 SSD 模型创建函数`build_ssd()`。 该模型在“图 11.5.1”中进行了说明。 该函数通过调用`base_outputs = backbone(inputs)`从骨干网或基础网络检索输出特征的`n_layers`。 - -在本书中,`backbone()`是`build_resnet()`。 `build_resnet()`可以生成的 ResNet 模型类似于“第 2 章”,“深度神经网络”中讨论的残差网络。 `build_resnet()`函数可以由构建基础网络的任何函数名称代替。 - -如图“图 11.5.1”所示,返回值`base_outputs`是输出特征的列表,这些特征将作为类别和偏移预测层的输入。 例如,第一输出`base_outputs[0]`用于生成`n[1]`类预测和`n[1]`偏移量预测。 - -在`build_ssd()`的`for`循环中,类别预测是`classes`变量,而偏移量预测是`offsets`变量。 在`for`循环迭代之后,将类别预测连接,并最终合并为一个具有以下尺寸的`classes`变量: - -![](img/B14853_11_055.png) - -对`offsets`变量执行相同的过程。 结果尺寸为: - -![](img/B14853_11_056.png) - -其中`n_mini_batch`是迷你批量大小,`n_anchor_box`是锚定框的数量。 `for`循环迭代的次数等于`n_layers`。 该数目也等于锚定框缩放因子的所需数目或 SSD 头的特征提取块的数目。 - -函数`build_ssd()`返回每个特征点或区域的锚框数量,每个前类的特征形状,偏移量预测层以及 SSD 模型本身。 - -“列表 11.8.1”:`model.py` - -```py -def build_ssd(input_shape, - backbone, - n_layers=4, - n_classes=4, - aspect_ratios=(1, 2, 0.5)): - """Build SSD model given a backbone - - Arguments: - input_shape (list): input image shape - backbone (model): Keras backbone model - n_layers (int): Number of layers of ssd head - n_classes (int): Number of obj classes - aspect_ratios (list): annchor box aspect ratios - - Returns: - n_anchors (int): Number of anchor boxes per feature pt - feature_shape (tensor): SSD head feature maps - model (Keras model): SSD model - """ - # number of anchor boxes per feature map pt - n_anchors = len(aspect_ratios) + 1 -``` - -```py - inputs = Input(shape=input_shape) - # no. of base_outputs depends on n_layers - base_outputs = backbone(inputs) - - outputs = [] - feature_shapes = [] - out_cls = [] - out_off = [] -``` - -```py - for i in range(n_layers): - # each conv layer from backbone is used - # as feature maps for class and offset predictions - # also known as multi-scale predictions - conv = base_outputs if n_layers==1 else base_outputs[i] - name = "cls" + str(i+1) - classes = conv2d(conv, - n_anchors*n_classes, - kernel_size=3, - name=name) -``` - -```py - # offsets: (batch, height, width, n_anchors * 4) - name = "off" + str(i+1) - offsets = conv2d(conv, - n_anchors*4, - kernel_size=3, - name=name) -``` - -```py - shape = np.array(K.int_shape(offsets))[1:] - feature_shapes.append(shape) - # reshape the class predictions, yielding 3D tensors of - # shape (batch, height * width * n_anchors, n_classes) - # last axis to perform softmax on them - name = "cls_res" + str(i+1) - classes = Reshape((-1, n_classes), - name=name)(classes) -``` - -```py - # reshape the offset predictions, yielding 3D tensors of - # shape (batch, height * width * n_anchors, 4) - # last axis to compute the (smooth) L1 or L2 loss - name = "off_res" + str(i+1) - offsets = Reshape((-1, 4), - name=name)(offsets) - # concat for alignment with ground truth size - # made of ground truth offsets and mask of same dim - # needed during loss computation - offsets = [offsets, offsets] - name = "off_cat" + str(i+1) - offsets = Concatenate(axis=-1, - name=name)(offsets) -``` - -```py - # collect offset prediction per scale - out_off.append(offsets) -``` - -```py - name = "cls_out" + str(i+1) -``` - -```py - #activation = 'sigmoid' if n_classes==1 else 'softmax' - #print("Activation:", activation) -``` - -```py - classes = Activation('softmax', - name=name)(classes) -``` - -```py - # collect class prediction per scale - out_cls.append(classes) -``` - -```py - if n_layers > 1: - # concat all class and offset from each scale - name = "offsets" - offsets = Concatenate(axis=1, - name=name)(out_off) - name = "classes" - classes = Concatenate(axis=1, - name=name)(out_cls) - else: - offsets = out_off[0] - classes = out_cls[0] -``` - -```py - outputs = [classes, offsets] - model = Model(inputs=inputs, - outputs=outputs, - name='ssd_head') -``` - -```py - return n_anchors, feature_shapes, model -``` - -如前面所述,与 MNIST 和 CIFAR-10 等小型数据集不同,SSD 中使用的映像很大。 因此,不可能将图像加载到张量变量中。 在下一节中,我们将介绍一个多线程数据生成器,该生成器将使我们能够从文件系统并发加载图像,并避免内存瓶颈。 - -# 9\. Keras 中的数据生成器模型 - -SSD 需要大量带标签的高分辨率图像来进行对象检测。 与之前的章节中使用的数据集可以加载到到内存中以训练模型不同,SSD 实现了多线程数据生成器。 多线程生成器的任务是加载图像的多个迷你批量及其相应的标签。 由于具有多线程,GPU 可以保持繁忙,因为一个线程向其提供数据,而其余的 CPU 线程处于队列中,准备从文件系统中馈入另一批数据或加载一批图像并计算基本真值 。“列表 11.9.1”显示了 Keras 中的数据生成器模型。 - -`DataGenerator`类继承自 Keras 的`Sequence`类,以确保它支持多处理。 `DataGenerator`保证在一个周期内使用整个数据集。 - -给定批量大小的整个周期的长度由`__len__()`方法返回。 对小批量数据的每个请求都可以通过`__getitem__()`方法来满足。 在每个周期之后,如果`self.shuffle`为`True`,则调用`on_epoch_end()`方法以随机播放整个批量。 - -“列表 11.9.1”:`data_generator.py` - -```py -class DataGenerator(Sequence): - """Multi-threaded data generator. - Each thread reads a batch of images and their object labels -``` - -```py - Arguments: - args: User-defined configuration - dictionary: Dictionary of image filenames and object labels - n_classes (int): Number of object classes - feature_shapes (tensor): Shapes of ssd head feature maps - n_anchors (int): Number of anchor boxes per feature map pt - shuffle (Bool): If dataset should be shuffled bef sampling - """ - def __init__(self, - args, - dictionary, - n_classes, - feature_shapes=[], - n_anchors=4, - shuffle=True): - self.args = args - self.dictionary = dictionary - self.n_classes = n_classes - self.keys = np.array(list(self.dictionary.keys())) - self.input_shape = (args.height, - args.width, - args.channels) - self.feature_shapes = feature_shapes - self.n_anchors = n_anchors - self.shuffle = shuffle - self.on_epoch_end() - self.get_n_boxes() -``` - -```py - def __len__(self): - """Number of batches per epoch""" - blen = np.floor(len(self.dictionary) / self.args.batch_size) - return int(blen) -``` - -```py - def __getitem__(self, index): - """Get a batch of data""" - start_index = index * self.args.batch_size - end_index = (index+1) * self.args.batch_size - keys = self.keys[start_index: end_index] - x, y = self.__data_generation(keys) - return x, y -``` - -```py - def on_epoch_end(self): - """Shuffle after each epoch""" - if self.shuffle == True: - np.random.shuffle(self.keys) -``` - -```py - def get_n_boxes(self): - """Total number of bounding boxes""" - self.n_boxes = 0 - for shape in self.feature_shapes: - self.n_boxes += np.prod(shape) // self.n_anchors - return self.n_boxes -``` - -数据生成器的大部分工作都是通过`__data_generation()`方法完成的,如“列表 11.9.2”所示。 给定一个小批量,该方法执行: - -* `imread()`从文件系统读取图像。 -* `labels = self.dictionary[key]`访问词典中存储的边界框和类标签。 前四个项目是边界框偏移量。 最后一个是类标签。 -* `anchor_boxes()`生成锚框。 -* `iou()`计算相对于地面真值边界框的每个锚点框的 IoU。 -* `get_gt_data()`为每个锚框分配地面真实等级和偏移量。 - -样本数据扩充函数也包括在内,但此处不再讨论,例如添加随机噪声,强度重新缩放和曝光调整。 `__data_generation()`返回输入`x`和输出`y`对,其中张量`x`存储输入图像,而张量`y`捆绑类,偏移量 ,和面具一起。 - -“列表 11.9.2”:`data_generator.py` - -```py -import layer_utils -``` - -```py -from skimage.io import imread - def __data_generation(self, keys): - """Generate train data: images and - object detection ground truth labels -``` - -```py - Arguments: - keys (array): Randomly sampled keys - (key is image filename) -``` - -```py - Returns: - x (tensor): Batch images - y (tensor): Batch classes, offsets, and masks - """ - # train input data - x = np.zeros((self.args.batch_size, *self.input_shape)) - dim = (self.args.batch_size, self.n_boxes, self.n_classes) - # class ground truth - gt_class = np.zeros(dim) - dim = (self.args.batch_size, self.n_boxes, 4) - # offsets ground truth - gt_offset = np.zeros(dim) - # masks of valid bounding boxes - gt_mask = np.zeros(dim) -``` - -```py - for i, key in enumerate(keys): - # images are assumed to be stored in self.args.data_path - # key is the image filename - image_path = os.path.join(self.args.data_path, key) - image = skimage.img_as_float(imread(image_path)) - # assign image to a batch index - x[i] = image - # a label entry is made of 4-dim bounding box coords - # and 1-dim class label - labels = self.dictionary[key] - labels = np.array(labels) - # 4 bounding box coords are 1st four items of labels - # last item is object class label - boxes = labels[:,0:-1] - for index, feature_shape in enumerate(self.feature_shapes): - # generate anchor boxes - anchors = anchor_boxes(feature_shape, - image.shape, - index=index, - n_layers=self.args.layers) - # each feature layer has a row of anchor boxes - anchors = np.reshape(anchors, [-1, 4]) - # compute IoU of each anchor box - # with respect to each bounding boxes - iou = layer_utils.iou(anchors, boxes) -``` - -```py - # generate ground truth class, offsets & mask - gt = get_gt_data(iou, - n_classes=self.n_classes, - anchors=anchors, - labels=labels, - normalize=self.args.normalize, - threshold=self.args.threshold) - gt_cls, gt_off, gt_msk = gt - if index == 0: - cls = np.array(gt_cls) - off = np.array(gt_off) - msk = np.array(gt_msk) - else: - cls = np.append(cls, gt_cls, axis=0) - off = np.append(off, gt_off, axis=0) - msk = np.append(msk, gt_msk, axis=0) -``` - -```py - gt_class[i] = cls - gt_offset[i] = off - gt_mask[i] = msk -``` - -```py - y = [gt_class, np.concatenate((gt_offset, gt_mask), axis=-1)] -``` - -```py - return x, y -``` - -现在我们有一个多线程生成器,我们可以用它来从文件系统加载图像。 在下一节中,我们将演示如何通过拍摄目标对象的图像并对其进行标记来构建自定义数据集。 - -# 10\. 示例数据集 - -使用便宜的 USB 相机(A4TECH PK-635G)收集了一个由 1,000 `640 X 480` RGB 训练图像和 50 `640 X 480` RGB 测试图像组成的小型数据集。 使用 **VGG 图像标注器**(**VIA**)[5]标记数据集图像,以检测三个对象:1)**汽水罐**,2)**果汁罐**和 3)**水瓶**。“图 11.10.1”显示了标记过程的示例 UI。 - -可以在`GitHub`存储库的`utils/video_capture.py`中找到用于收集图像的工具脚本。 该脚本每 5 秒自动捕获一次图像,因此可以加快数据收集过程。 - -![](img/B14853_11_11.png) - -图 11.10.1 使用 VGG 图像标注器(VIA)进行数据集标记的过程 - -数据收集和标记是一项耗时的活动。 在行业中,通常将其外包给第三方标注公司。 使用自动数据标记软件是加快数据标记任务的另一种选择。 - -有了这个示例数据集,我们现在可以训练我们的对象检测网络。 - -# 11\. SSD 模型训练 - -[可以从以下链接下载包含 csv 格式标签的 train 和测试数据集](https://bit.ly/adl2-ssd)。 - -在顶层文件夹(即“第 11 章”,“对象检测”)中,创建数据集文件夹,将下载的文件复制到此处,然后运行以下命令将其解压缩: - -```py -mkdir dataset -cp drinks.tar.gz dataset -cd dataset -tar zxvf drinks.tar.gz -cd.. -``` - -通过执行以下步骤,将 SSD 模型训练 200 个周期: - -```py -python3 ssd-11.6.1.py --train -``` - -可以根据 GPU 内存调整默认的批量大小`--batch-size=4`。 在 1080Ti 上,批量大小为 2。在 32GB V100 上,每个 GPU 可以为 4 或 8。 `--train`代表模型训练选项。 - -为了支持边界框偏移量的归一化,包含`--normalize`选项。 为了使用改进的损失函数,添加了`--improved_loss`选项。 如果仅需要平滑的 L1(无焦点损失),请使用`–smooth-l1`。 为了显示: - -* L1,无规范化: - * `python3 ssd-11.1.1.py –-train` -* 改进的损失函数,无规范化: - * `python3 ssd-11.1.1.py –-train --improved-loss` -* 改进的损失函数,具有规范化: - * `python3 ssd-11.1.1.py –-train –improved-loss --normalize` -* 平滑 L1,具有规范化: - * `python3 ssd-11.1.1.py –-train –-smooth-l1 --normalize` - -训练完 SSD 网络之后,我们需要解决另一个问题。 我们如何处理给定对象的多个预测? 在测试训练好的模型之前,我们将首先讨论**非最大抑制**(**NMS**)算法。 - -# 12\. 非最大抑制(NMS)算法 - -模型训练完成后,网络将预测边界框偏移量和相应的类别。 在某些情况下,两个或更多边界框引用同一对象,从而创建冗余预测。 图 11.12.1 中的**汽水罐**表示了这种情况。 为了删除多余的预测,将调用 NMS 算法。 本书涵盖了经典 NMS 和软 NMS [6],如“算法 11.12.1”中所示。 两种算法都假定边界框和相应的置信度得分或概率是已知的。 - -![](img/B14853_11_12.png) - -图 11.12.1 网络预测了汽水罐对象的两个重叠边界框。 只选择一个有效的边界框,即得分为 0.99 的边界框。 - -在经典 NMS 中,基于概率选择最终边界框,并将其存储在列表`D`中,并带有相应的分数`S`。 所有边界框和相应的概率都存储在初始列表`B`和`P`中。 在第 3 行和第 4 行中,将具有最高分数`p[m]`的边界框用作参考,`b[m]`。 - -参考边界框被添加到最终选择的边界框`D`的列表中,并从列表`B`中删除,如第 5 行所示。 并且列表`S`从`P`中删除。 对于其余边界框,如果 *IoU* 与`b[m]`大于或等于设置的阈值`N[t]`,将其从`B`中删除。 其相应的分数也从`P`中删除。 - -步骤在第 6 和 9-11 行中显示。 这些步骤将删除所有分数较小的冗余边界框。 在检查完所有其余的边界框之后,重复从第 3 行开始的过程。 该过程继续进行,直到边界框`B`的列表为空。 该算法返回选定的边界框`D`和相应的分数`S`。 - -经典 NMS 的问题是边界盒包含另一个对象,但其中的 *IoU* 和`b[m]`会从列表中删除。 Soft NMS [6]提出,与其从列表中彻底删除,不如以`b[m]`,如第 8 行所示。 - -重叠的边界框具有第二次机会。 IoU 较小的边界框在将来的迭代中具有更高的生存机会。 在将来的选择中,它实际上可能证明它包含一个与`b[m]`不同的对象。 如“算法 11.12.1”中所示,Soft NMS 是传统 NMS 的便捷替代。 无需重新训练 SSD 网络。 与经典 NMS 相比,Soft NMS 具有更高的平均精度。 - -“列表 11.12.1”说明了经典 NMS 和软 NMS。 除了最终的边界框和相应的分数外,还返回相应的对象。 当其余边界框的最大分数小于某个阈值(例如:0.2)时,该代码将实现 NMS 的提前终止。 - -“算法 11.12.1”**NMS 和软 NMS** - -**要求**:边界框预测:`B = {b[1], b[2], …, b[n]}` - -**要求**:边界框类别的置信度或分数:`B = {b[1], b[2], …, b[n]}` - -**要求**:最小 NMS *IoU* 阈值:`N[t]` - -1. `D <- {}`;`S <- {}` -2. 当`B ≠ empty`,执行 -3. `m <- argmax P` -4. `M <- b[m]`; `N <- p[m]`, -5. `D <- D ∪ M`;`B <- B - M`;`S <- S ∪ N`;`P <- P - N`; -6. 对于步骤`b[i] ∈ B`,执行 -7. 如果`soft_NMS = True` -8. `p[i] = p[i] exp(-IOU(M, b[i])^2 / σ)` -9. 否则如果`IOU(M, b[i]) >= N[t]`,那么 -10. `B = B - b[i]`;`P = P - p[i]` -11. 结束 -12. 结束 -13. 结束 - -1. 返回`D, S` - -“列表 11.12.1”:`boxes.py` - -```py -def nms(args, classes, offsets, anchors): - """Perform NMS (Algorithm 11.12.1). -``` - -```py - Arguments: - args: User-defined configurations - classes (tensor): Predicted classes - offsets (tensor): Predicted offsets - - Returns: - objects (tensor): class predictions per anchor - indexes (tensor): indexes of detected objects - filtered by NMS - scores (tensor): array of detected objects scores - filtered by NMS - """ -``` - -```py - # get all non-zero (non-background) objects - objects = np.argmax(classes, axis=1) - # non-zero indexes are not background - nonbg = np.nonzero(objects)[0] -``` - -```py - # D and S indexes in Line 1 - indexes = [] - while True: - # list of zero probability values - scores = np.zeros((classes.shape[0],)) - # set probability values of non-background - scores[nonbg] = np.amax(classes[nonbg], axis=1) -``` - -```py - # max probability given the list - # Lines 3 and 4 - score_idx = np.argmax(scores, axis=0) - score_max = scores[score_idx] -``` - -```py - # get all non max probability & set it as new nonbg - # Line 5 - nonbg = nonbg[nonbg != score_idx] -``` - -```py - # if max obj probability is less than threshold (def 0.8) - if score_max < args.class_threshold: - # we are done - break - - # Line 5 - indexes.append(score_idx) - score_anc = anchors[score_idx] - score_off = offsets[score_idx][0:4] - score_box = score_anc + score_off - score_box = np.expand_dims(score_box, axis=0) - nonbg_copy = np.copy(nonbg) -``` - -```py - # get all overlapping predictions (Line 6) - # perform Non-Max Suppression (NMS) - for idx in nonbg_copy: - anchor = anchors[idx] - offset = offsets[idx][0:4] - box = anchor + offset - box = np.expand_dims(box, axis=0) - iou = layer_utils.iou(box, score_box)[0][0] - # if soft NMS is chosen (Line 7) - if args.soft_nms: - # adjust score: Line 8 - iou = -2 * iou * iou - classes[idx] *= math.exp(iou) - # else NMS (Line 9), (iou threshold def 0.2) - elif iou >= args.iou_threshold: - # remove overlapping predictions with iou>threshold - # Line 10 - nonbg = nonbg[nonbg != idx] -``` - -```py - # Line 2, nothing else to process - if nonbg.size == 0: - break -``` - -```py - # get the array of object scores - scores = np.zeros((classes.shape[0],)) - scores[indexes] = np.amax(classes[indexes], axis=1) -``` - -```py - return objects, indexes, scores -``` - -假设我们具有训练有素的 SSD 网络和一种抑制冗余预测的方法,则下一节将讨论对测试数据集的验证。 基本上,我们想知道我们的 SSD 是否可以对从未见过的图像执行对象检测。 - -# 13\. SSD 模型验证 - -在对 SSD 模型进行 200 个周期的训练之后,可以验证表现。 用于评估的三个可能指标:1)**IoU**,2)**精度**和 3)**召回**。 - -第一个指标是**平均 IoU**(**mIoU**)。 给定真实情况测试数据集,计算真实情况边界框和预测边界框之间的 IoU。 在执行 NMS 之后,对所有真实情况和预测的边界框执行此操作。 所有 IoU 的平均值计算为 mIoU: - -![](img/B14853_11_074.png) (Equation 11.13.1) - -其中`n_box`是地面真值边界框`b[i]`的数量和`n_pred`是预测边界框`d[j]`的数量。 请注意,该度量标准无法验证两个重叠的边界框是否属于同一类。 如果需要,则可以轻松修改代码。“列表 11.13.1”显示了代码实现。 - -第二个度量是**精度**,如“公式 11.3.2”所示。 它是正确预测的对象类别的数量(真阳性或 TP)除以正确预测的对象类别的数量(真阳性或 TP)与错误预测的对象类别的数量(假阳性或 FP)之和。 精度是衡量 SSD 正确识别图像中对象的表现的指标。 精度越接近 1.0 越好。 - -![](img/B14853_11_075.png) (Equation 11.3.2) - -第三个度量是**召回**,如“公式 11.3.3”所示。 它是正确预测的对象类别的数量(真阳性或 TP)除以正确预测的对象类别的数量(真阳性或 TP)加上错过的对象数量(假阴性或 FN)之和。 召回率是衡量 SSD 在不对图像中的对象进行错误分类方面有多出色的度量。 召回率越接近 1.0,则越好。 - -![](img/B14853_11_076.png) (Equation 11.3.3) - -如果我们对测试数据集中的所有图像取均值,则它们称为平均精度和平均召回率。 在目标检测中,使用不同 mIoU 的精度和召回曲线来衡量表现。 为了简单起见,我们仅针对特定类别阈值(默认值为 0.5)计算这些指标的值。 感兴趣的读者可以参考 Pascal VOC [7]文章,以获取有关对象检测指标的更多详细信息。 - -评价结果示于“表 11.13.1”。 结果可以通过运行: - -* 无规范化: - * `python3 ssd-11.6.1.py --restore-weights=ResNet56v2-4layer-extra_anchors-drinks-200.h5 --evaluate` -* 无规范化,平滑 L1: - * `python3 ssd-11.6.1.py --restore-weights=ResNet56v2-4layer-smooth_l1-extra_anchors-drinks-200.h5 --evaluate` -* 具有规范化: - * `python3 ssd-11.6.1.py --restore-weights=ResNet56v2-4layer-norm-extra_anchors-drinks-200.h5 --evaluate --normalize` -* 具有规范化,平滑 L1: - * `python3 ssd-11.6.1.py --restore-weights=ResNet56v2-4layer-norm-smooth_l1-extra_anchors-drinks-200.h5 --evaluate --normalize` -* 具有规范化,平滑 L1,焦点损失: - * `python3 ssd-11.6.1.py --restore-weights=ResNet56v2-4layer-norm-improved_loss-extra_anchors-drinks-200.h5 --evaluate --normalize` - -权重在 GitHub 上可用。 - -在 mIoU 上,最佳表现是非归一化偏移选项,而归一化偏移设置具有最高的平均精度和召回率。 考虑到训练数据集中只有 1,000 张图像,表现并不是最新技术。 也没有应用数据扩充。 - -从结果来看,使用损失函数的改进会降低表现。 使用平滑 L1 或焦距损失函数或同时使用两者时,会发生这种情况。“图 11.13.1”至“图 11.13.5”显示了样本预测。 可以通过执行以下操作获得图像上的对象检测: - -```py -python3 ssd-11.6.1.py –-restore-weights= ---image-file= --evaluate -``` - -例如,要在`dataset/drinks/0010050.jpg`上运行对象检测: - -```py -python3 ssd-11.6.1.py --restore-weights=ResNet56v2-4layer-extra_anchors-drinks-200.h5 --image-file=dataset/drinks/0010050.jpg --evaluate -``` - -如果模型权重文件名中包含单词`norm`,请附加`--normalize option`。 - -“列表 11.13.1”:`ssd-11.6.1.py` - -```py - def evaluate_test(self): - # test labels csv path - path = os.path.join(self.args.data_path, - self.args.test_labels) - # test dictionary - dictionary, _ = build_label_dictionary(path) - keys = np.array(list(dictionary.keys())) - # sum of precision - s_precision = 0 - # sum of recall - s_recall = 0 - # sum of IoUs - s_iou = 0 - # evaluate per image - for key in keys: - # ground truth labels - labels = np.array(dictionary[key]) - # 4 boxes coords are 1st four items of labels - gt_boxes = labels[:, 0:-1] - # last one is class - gt_class_ids = labels[:, -1] - # load image id by key - image_file = os.path.join(self.args.data_path, key) - image = skimage.img_as_float(imread(image_file)) - image, classes, offsets = self.detect_objects(image) - # perform nms - _, _, class_ids, boxes = show_boxes(args, - image, - classes, - offsets, - self.feature_shapes, - show=False) -``` - -```py - boxes = np.reshape(np.array(boxes), (-1,4)) - # compute IoUs - iou = layer_utils.iou(gt_boxes, boxes) - # skip empty IoUs - if iou.size ==0: - continue - # the class of predicted box w/ max iou - maxiou_class = np.argmax(iou, axis=1) -``` - -```py - # true positive - tp = 0 - # false positiove - fp = 0 - # sum of objects iou per image - s_image_iou = [] - for n in range(iou.shape[0]): - # ground truth bbox has a label - if iou[n, maxiou_class[n]] > 0: - s_image_iou.append(iou[n, maxiou_class[n]]) - # true positive has the same class and gt - if gt_class_ids[n] == class_ids[maxiou_class[n]]: - tp += 1 - else: - fp += 1 -``` - -```py - # objects that we missed (false negative) - fn = abs(len(gt_class_ids) - tp) - s_iou += (np.sum(s_image_iou) / iou.shape[0]) - s_precision += (tp/(tp + fp)) - s_recall += (tp/(tp + fn)) -``` - -```py - n_test = len(keys) - print_log("mIoU: %f" % (s_iou/n_test), - self.args.verbose) - print_log("Precision: %f" % (s_precision/n_test), - self.args.verbose) - print_log("Recall: %f" % (s_recall/n_test), - self.args.verbose) -``` - -结果如下,在“表 11.13.1”中: - -| | **未归一化的偏移** | **未归一化的偏移,平滑 L1** | **归一化的偏移** | **归一化偏移,平滑 L1** | **归一化偏移,平滑 L1,焦点损失** | -| --- | --- | --- | --- | --- | --- | -| IoU | 0.64 | 0.61 | 0.53 | 0.50 | 0.51 | -| 平均精度 | 0.87 | 0.86 | 0.90 | 0.85 | 0.85 | -| 平均召回率 | 0.87 | 0.85 | 0.87 | 0.83 | 0.83 | - -表 11.13.1 测试数据集上 SSD 的表现基准。 - -![](img/B14853_11_13.png) - -图 11.13.1 来自测试数据集的图像上的示例预测示例(未归一化的偏移量)。 - -![](img/B14853_11_14.png) - -图 11.13.2 来自测试数据集的图像上的示例预测示例(未归一化的偏移量,平滑 L1)。 - -![](img/B14853_11_15.png) - -图 11.13.3 来自测试数据集的图像预测示例(标准化偏移)。 - -![](img/B14853_11_16.png) - -图 11.13.4 对来自测试数据集的图像进行的预测示例(标准化偏移,平滑 L1)。 - -![](img/B14853_11_17.png) - -图 11.13.5 对来自测试数据集的图像进行的预测示例(归一化偏移,平滑 L1,聚焦损失)。 - -本节中的结果验证了我们的 SSD 模型。 一个重要的经验教训是,只要我们理解了问题,无论问题多么复杂,我们都可以逐步构建一个可行的解决方案。 SSD 是迄今为止我们在本书中介绍过的最复杂的模型。 它需要许多工具,模块以及大量数据准备和管理才能工作。 - -# 14\. 总结 - -在本章中,讨论了多尺度单发对象检测的概念。 使用以接收场斑块的质心为中心的锚框,可以计算地面真值边界框偏移量。 代替原始像素误差,归一化像素误差会鼓励更适合优化的有限范围。 - -每个锚框都分配有地面实况类别标签。 如果锚点框不与对象重叠,则为其分配背景类,并且其偏移量不包括在偏移量损失计算中。 已经提出了焦点损失以改善类别损失函数。 可以使用平滑的 L1 损失函数代替默认的 L1 偏置损失函数。 - -对测试数据集的评估表明,使用默认损失函数的归一化偏移可实现平均精度和召回率方面的最佳表现,而当消除偏移归一化时,mIoU 会得到改善。 通过增加训练图像的数量和变化可以提高性能。 - -在“第 12 章”中,“语义分割”建立在本章中开发的概念的基础上。 特别是,我们重用 ResNet 骨干网络来构建分段网络和 IoU 指标进行验证。 - -# 15\. 参考 - -1. `Krizhevsky Alex, Ilya Sutskever, and Geoffrey E. Hinton. "Imagenet classification with deep convolutional neural networks." Advances in neural information processing systems. 2012.` -1. `Liu Wei, et al. "SSD: Single Shot MultiBox Detector." European conference on computer vision. Springer, Cham, 2016.` -1. `Girshick Ross. "Fast R-CNN." Proceedings of the IEEE international conference on computer vision. 2015.` -1. `Lin Tsung-Yi, et al. "Focal loss for Dense Object Detection. "Proceedings of the IEEE international conference on computer vision. 2017.` -1. `Dutta, et al. VGG Image Annotator http://www.robots.ox.ac.uk/~vgg/software/via/` -1. `Bodla Navaneeth, et al. "Soft-NMS--Improving Object Detection With One Line of Code." Proceedings of the IEEE international conference on computer vision. 2017.` -1. `Everingham Mark, et al. "The Pascal Visual Object Classes (VOC) challenge." International journal of computer vision 88.2 (2010): 303-338.` -1. `"Huber Loss." https://en.wikipedia.org/wiki/Huber_loss` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/12.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/12.md deleted file mode 100644 index 00f0bb79..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/12.md +++ /dev/null @@ -1,420 +0,0 @@ -# 十二、语义分割 - -在“第 11 章”,“对象检测”中,我们讨论了对象检测作为一种重要的计算机视觉算法,具有多种实际应用。 在本章中,我们将讨论另一种称为语义分割的相关算法。 如果对象检测的目的是对图像中的每个对象同时执行定位和标识,则在语义分割中,目的是根据每个像素的对象类别对它们进行分类。 - -进一步扩展类比,在对象检测中,我们使用边界框显示结果。 在语义分割中,同一对象的所有像素都属于同一类别。 在视觉上,同一对象的所有像素将具有相同的颜色。 例如,属于*汽水**类别的所有像素均为蓝色。 非苏打罐对象的像素将具有不同的颜色。 - -类似于对象检测,语义分割有许多实际应用。 在医学成像中,它可用于分离和测量正常细胞与异常细胞的区域。 在卫星成像中,语义分段可用于度量森林覆盖率或灾难期间的洪水程度。 通常,语义分割可用于识别属于同一类对象的像素。 识别每个对象的各个实例并不重要。 - -好奇的读者可能会想知道,一般而言,不同的分割算法与特别是语义分割算法之间有什么区别? 在以下部分中,我们将对不同的分割算法进行限定。 - -总而言之,本章的目的是为了提出: - -* 不同类型的分割算法 -* **全卷积网络**(**FCN**)作为语义分割算法的实现 -* `tf.keras`中 FCN 的实现和评估 - -我们将从讨论不同的分割算法开始。 - -# 1\. 分割 - -分割算法将图像划分为像素或区域集。 分区的目的是为了更好地理解图像表示的内容。 像素组可以表示图像中特定应用感兴趣的对象。 我们划分的方式区分了不同的分割算法。 - -在某些应用中,我们对给定图像中的特定可数对象感兴趣。 例如,在自主导航中,我们对车辆,交通标志,行人和道路上的其他物体的实例感兴趣。 这些可计数对象统称为,称为**事物**。 所有其他像素都集中在一起作为背景。 这种类型的细分称为**实例细分**。 - -在其他应用中,我们对可数对象不感兴趣,而对无定形的不可数区域感兴趣,例如天空,森林,植被,道路,草地,建筑物和水体。 这些对象统称为东西。 这种类型的分段称为**语义分段**。 - -大致上,**事物**和**事物**共同构成了整个图像。 如果算法可以识别事物像素和填充像素,则其称为**全光分割**,如 Kirilov 等人所定义 [1]。 - -但是,事物与事物之间的区别并不严格。 应用可能将可数对象统称为东西。 例如,在百货商店中,不可能识别机架上的服装实例。 它们可以作为布料一起集中在一起。 - -“图 12.1.1”显示了不同类型的细分之间的区别。 输入的图像在桌子的顶部显示了两个汽水罐和两个果汁罐。 背景杂乱无章。 假设我们只对汽水罐和果汁罐感兴趣,在实例细分中,我们为每个对象实例分配唯一的颜色以分别区分四个对象。 对于语义分割,我们假设将所有的汽水罐都塞在一起,将果汁罐作为另一罐塞在一起,将背景作为最后的罐塞在一起。 基本上,我们为每种物料分配了唯一的颜色。 最后,在全景分割中,我们假设只有背景才是背景,而我们只对苏打水和果汁罐感兴趣。 - -对于这本书,我们仅探讨语义分割。 按照“图 12.1.1”中的示例,我们将为“第 11 章”,“对象检测”中使用的对象分配唯一的填充类别:1)水瓶,2)**汽水罐**和 3)**果汁罐**。 第四个也是最后一个类别是背景。 - -![A close up of a bottle Description automatically generated](img/B14853_12_01.png) - -![A picture containing indoor Description automatically generated](img/B14853_12_02.png) - -![A close up of a logo Description automatically generated](img/B14853_12_03.png) - -![A screenshot of a cell phone Description automatically generated](img/B14853_12_04.png) - -图 12.1.1:显示不同分割算法的四幅图像。 彩色效果最佳。 原始图像可以在[这个页面](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter12-segmentation)中找到。 - -# 2\. 语义分割网络 - -从上一节中,我们了解到语义分割网络是一个像素级分类器。 网络框图显示在“图 12.2.1”中。 但是,与简单分类器不同(例如,“第 1 章”,“Keras 深度神经网络”和“第 2 章”,“MNIST 分类器简介”) 其中只有一个分类器生成`one-hot vector`作为输出,在语义分段中,我们有并行运行的并行分类器。 每个人都在生成自己的单热点向量预测。 分类器的数量等于输入图像中的像素数量或图像宽度与高度的乘积。 每个`one-hot vector`预测的维数等于感兴趣的填充对象类别的数量。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_12_05.png) - -图 12.2.1:可以将语义分割网络视为按像素分类器。 彩色效果最佳。 原始图像可以在[这个页面](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter12-segmentation)中找到 - -例如,假设我们对以下四个类别感兴趣:0)**背景**,1)**水瓶**,2)**汽水罐**和 3)**果汁罐**,我们可以在“图 12.2.2”中看到,每个对象类别有四个像素。 - -相应地,使用 4 维`one-hot vector`对每个像素进行分类。 我们使用阴影表示像素的类别。 利用这一知识,我们可以想象一个语义分割网络预测`image_width x image_height` 4 维一热向量作为输出,每个像素一个 4 维一热向量: - -![A bottle of water on a table Description automatically generated](img/B14853_12_06.png) - -图 12.2.2:四个不同的样本像素。 使用 4 维一热向量,每个像素根据其类别进行分类。 彩色效果最佳。 原始图像可以在[这个页面](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter12-segmentation)中找到 - -了解了语义分割的概念后,我们现在可以介绍神经网络像素级分类器。 Long 等人的《全卷积网络(FCN)》启发了我们的语义分段网络架构 [2]。FCN 的关键思想是在生成最终预测时使用多个比例的特征映射。 - -我们的语义分段网络显示在“图 12.2.3”中。 它的输入是 RGB 图像(例如`640 x 480 x 3`),并且输出具有类似尺寸的张量,但最后一个尺寸是填充类别的数量(例如,对于 4 种填充类别而言是`640 x 480 x 4`)。 出于可视化目的,我们通过为每种类别分配颜色来将输出映射到 RGB: - -![A screenshot of a cell phone Description automatically generated](img/B14853_12_07.png) - -图 12.2.3:语义分割的网络架构。 除非另有说明,否则核大小为 3。 除非另有说明,否则跨步为 1。 彩色效果最佳。 原始图像可以在[这个页面](https://github.com/PacktPublishing/Advanced-Deep-Learning-with-Keras/tree/master/chapter12-segmentation)中找到 - -类似于“第 11 章”,“对象检测”中讨论的 SSD,我们采用骨干网作为特征提取器。 我们在 SSD 中使用类似的 ResNetv2 网络。 ResNet 主干网执行两次最大池化,以到达第一组特征映射,其尺寸为输入图像的 1/4。 通过使用连续的`Conv2D(strides=2)-BN-ReLU`层生成其他特征映射集,从而生成具有输入图像尺寸`(1/8, 1/16, 1/32)`的特征映射。 - -Zhao 等人的《金字塔场景解析网络(PSPNet)》进行了改进,进一步增强了我们的语义分割网络架构 [3]。 在 PSPNet 中,每个特征映射由另一个卷积层进一步处理。 此外,还使用了第一组特征映射。 - -FCN 和 PSPNet 都对特征金字塔进行了上采样,以达到与第一组特征映射相同的大小。 之后,使用`Concatenate`层将所有上采样特征融合在一起。 然后级联层通过步长等于 2 的转置卷积处理两次,以恢复原始图像的宽度和高度。 最后,使用核大小为 1 且过滤器等于 4(换句话说,类别数)和`Softmax`层的转置卷积生成按像素分类预测。 - -在下一节中,我们将讨论细分网络的`tf.keras`实现。 我们可以重用“第 11 章”,“对象检测”中的 SSD 中的某些网络块,以加快实现速度。 - -# 3\. Keras 中的语义分割网络 - -如图“图 12.2.3”所示,我们已经有了语义细分网络的一些关键构建块。 我们可以重用“第 2 章”,“深度神经网络”中介绍的 ResNet 模型。 我们只需要构建特征的金字塔以及上采样和预测层。 - -借用我们在“第 2 章”,“深度神经网络”中开发的 ResNet 模型,并在“第 11 章”,“对象检测”中重用了该模型, 我们提取具有四个级别的特征金字塔。“列表 12.3.1”显示了从 ResNet 提取特征的金字塔。 `conv_layer()`只是创建`Conv2D(strides=2)-BN-ReLU`层的辅助函数。 - -“列表 12.3.1”:`resnet.py`: - -特征的金字塔函数: - -```py -def features_pyramid(x, n_layers): - """Generate features pyramid from the output of the - last layer of a backbone network (e.g. ResNetv1 or v2) -``` - -```py - Arguments: - x (tensor): Output feature maps of a backbone network - n_layers (int): Number of additional pyramid layers -``` - -```py - Return: - outputs (list): Features pyramid - """ - outputs = [x] - conv = AveragePooling2D(pool_size=2, name='pool1')(x) - outputs.append(conv) - prev_conv = conv - n_filters = 512 -``` - -```py - # additional feature map layers - for i in range(n_layers - 1): - postfix = "_layer" + str(i+2) - conv = conv_layer(prev_conv, - n_filters, - kernel_size=3, - strides=2, - use_maxpool=False, - postfix=postfix) - outputs.append(conv) - prev_conv = conv -``` - -```py - return outputs -``` - -“列表 12.3.1”只是特征金字塔的一半。 剩下的一半是每组特征之后的卷积。 另一半显示在“列表 12.3.2”中,以及金字塔各层的上采样。 例如,图像尺寸为 1/8 的特征会被上采样 2 倍,以使其尺寸与图像尺寸为 1/4 的第一组特征相匹配。 在同一清单中,我们还建立了完整的分割模型,从骨干网络到特征金字塔,再连接上采样特征金字塔,最后进一步进行特征提取,上采样和预测。 我们在输出层使用`n`维(例如 4 维)`Softmax`层执行逐像素分类。 - -“列表 12.3.2”:`model.py`: - -构建语义分割网络: - -```py -def build_fcn(input_shape, - backbone, - n_classes=4): - """Helper function to build an FCN model. - - Arguments: - backbone (Model): A backbone network - such as ResNetv2 or v1 - n_classes (int): Number of object classes - including background. - """ -``` - -```py - inputs = Input(shape=input_shape) - features = backbone(inputs) -``` - -```py - main_feature = features[0] - features = features[1:] - out_features = [main_feature] - feature_size = 8 - size = 2 - # other half of the features pyramid - # including upsampling to restore the - # feature maps to the dimensions - # equal to 1/4 the image size - for feature in features: - postfix = "fcn_" + str(feature_size) - feature = conv_layer(feature, - filters=256, - use_maxpool=False, - postfix=postfix) - postfix = postfix + "_up2d" - feature = UpSampling2D(size=size, - interpolation='bilinear', - name=postfix)(feature) - size = size * 2 - feature_size = feature_size * 2 - out_features.append(feature) -``` - -```py - # concatenate all upsampled features - x = Concatenate()(out_features) - # perform 2 additional feature extraction - # and upsampling - x = tconv_layer(x, 256, postfix="up_x2") - x = tconv_layer(x, 256, postfix="up_x4") - # generate the pixel-wise classifier - x = Conv2DTranspose(filters=n_classes, - kernel_size=1, - strides=1, - padding='same', - kernel_initializer='he_normal', - name="pre_activation")(x) - x = Softmax(name="segmentation")(x) -``` - -```py - model = Model(inputs, x, name="fcn") -``` - -```py - return model -``` - -给定分割网络模型,我们使用学习速度为`1e-3`的 Adam 优化器和分类交叉熵损失函数来训练网络。“列表 12.3.3”显示了模型构建和训练函数调用。 在 40 个周期之后,学习率每 20 个周期减半。 我们使用`AccuracyCallback`监视网络表现,类似于“第 11 章”,“对象检测”中的 SSD 网络。 回调使用类似于对象检测平均 IoU 的**平均 IoU**(**mIoU**)指标计算表现。 表现最佳的平均值 IoU 的权重保存在文件中。 通过调用`fit_generator()`将网络训练 100 个周期。 - -“列表 12.3.3”:`fcn-12.3.1.py`: - -语义分割网络的初始化和训练: - -```py - def build_model(self): - """Build a backbone network and use it to - create a semantic segmentation - network based on FCN. - """ -``` - -```py - # input shape is (480, 640, 3) by default - self.input_shape = (self.args.height, - self.args.width, - self.args.channels) -``` - -```py - # build the backbone network (eg ResNet50) - # the backbone is used for 1st set of features - # of the features pyramid - self.backbone = self.args.backbone(self.input_shape, - n_layers=self.args.layers) -``` - -```py - # using the backbone, build fcn network - # output layer is a pixel-wise classifier - self.n_classes = self.train_generator.n_classes - self.fcn = build_fcn(self.input_shape, - self.backbone, - self.n_classes) -``` - -```py - def train(self): - """Train an FCN""" - optimizer = Adam(lr=1e-3) - loss = 'categorical_crossentropy' - self.fcn.compile(optimizer=optimizer, loss=loss) -``` - -```py - log = "# of classes %d" % self.n_classes - print_log(log, self.args.verbose) - log = "Batch size: %d" % self.args.batch_size - print_log(log, self.args.verbose) -``` - -```py - # prepare callbacks for saving model weights - # and learning rate scheduler - # model weights are saved when test iou is highest - # learning rate decreases by 50% every 20 epochs - # after 40th epoch - accuracy = AccuracyCallback(self) - scheduler = LearningRateScheduler(lr_scheduler) -``` - -```py - callbacks = [accuracy, scheduler] - # train the fcn network - self.fcn.fit_generator(generator=self.train_generator, - use_multiprocessing=True, - callbacks=callbacks, - epochs=self.args.epochs, - workers=self.args.workers) -``` - -多线程数据生成器类`DataGenerator`与“第 11 章”,“对象检测”中使用的类类似。 如“列表 12.3.4”所示,对`__data_generation(self, keys)`签名方法进行了修改,以生成一对图像张量及其相应的按像素方向的真实情况标签或分割蒙版 。 在下一节中,我们将讨论如何生成基本事实标签。 - -“列表 12.3.4”:`data_generator.py`: - -`DataGenerator`类用于语义分割的数据生成方法: - -```py - def __data_generation(self, keys): - """Generate train data: images and - segmentation ground truth labels -``` - -```py - Arguments: - keys (array): Randomly sampled keys - (key is image filename) -``` - -```py - Returns: - x (tensor): Batch of images - y (tensor): Batch of pixel-wise categories - """ - # a batch of images - x = [] - # and their corresponding segmentation masks - y = [] -``` - -```py - for i, key in enumerate(keys): - # images are assumed to be stored - # in self.args.data_path - # key is the image filename - image_path = os.path.join(self.args.data_path, key) - image = skimage.img_as_float(imread(image_path)) - # append image to the list - x.append(image) - # and its corresponding label (segmentation mask) - labels = self.dictionary[key] - y.append(labels) -``` - -```py - return np.array(x), np.array(y) -``` - -语义分割网络现已完成。 使用`tf.keras`,我们讨论了其架构实现,初始化和训练。 - -在运行训练程序之前,我们需要训练和测试带有地面真实性标签的数据集。 在的下一部分中,我们将讨论将在本章中使用的语义分割数据集。 - -# 4\. 示例数据集 - -我们可以使用在“第 11 章”,“对象检测”中使用的数据集。 回想一下,我们使用了一个小型数据集,其中包含使用便宜的 USB 相机(A4TECH PK-635G)收集的 1,000 `640 x 480` RGB 训练图像和 50 `640 x 480` RGB 测试图像。 但是,我们没有使用边界框和类别进行标记,而是使用多边形形状跟踪了每个对象类别的边缘。 我们使用相同的数据集标注器 **VGG 图像标注器**(**VIA**)[4]手动跟踪边缘并分配以下标签:1)**水瓶**,2)**汽水罐**和 3)**果汁罐**。 - -“图 12.4.1”显示了标记过程的示例 UI。 - -![A picture containing indoor, bottle, appliance, wall Description automatically generated](img/B14853_12_08.png) - -图 12.4.1:使用 VGG 图像标注器(VIA)进行语义分割的数据集标记过程 - -威盛标签软件将标签保存在 JSON 文件中。 对于训练和测试数据集,这些是: - -```py -segmentation_train.json -segmentation_test.json -``` - -无法原样使用存储在 JSON 文件中的多边形区域。 每个区域都必须转换成分割蒙版,即张量,其尺寸为`img_w x img_h x px – wise_category`。 在此数据集中,分割蒙版的尺寸为`640 x 480 x 4`。类别 0 为背景,其余为 1)对于**水瓶**,2)对于**苏打罐**,以及 3)表示**果汁罐**。 在`utils`文件夹中,我们创建了一个`generate_gt_segmentation.py`工具,用于将 JSON 文件转换为分段掩码。 为了方便起见,用于训练和测试的地面真实数据存储在压缩数据集中,该数据集是从[上一章](https://bit.ly/adl2-ssd)下载的: - -```py -segmentation_train.npy -segmentation_test.npy -``` - -每个文件都包含`image filename: segmentation mask`格式的真实情况数据字典,该字典在训练和验证期间加载。“图 12.4.2”显示了使用彩色像素可视化的“图 12.4.1”中图像的分割蒙版的示例。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_12_09.png) - -图 12.4.2:可视化图 12.4.1 中所做标注的分段蒙版 - -现在,我们准备训练和验证语义分割网络。 在下一节中,我们将显示在本节中标注的数据集上语义分割的结果。 - -# 5\. 语义分割验证 - -要训​​练语义分段网络,请运行以下命令: - -```py -python3 fcn-12.3.1.py --train -``` - -在每个周期,也会执行验证以确定表现最佳的参数。 对于语义分割,可以使用两个度量。 首先是平均 IOU。 这类似于上一章中目标检测中的平均 IoU。 区别在于针对每个填充类别在真实情况分割掩码和预测的分割掩码之间计算 IoU。 这包括背景。 平均 IoU 只是测试数据集所有 IoU 的平均值。 - -“图 12.5.1”显示了在每个周期使用 mIoU 的语义分割网络的表现。 最大 mIoU 为 0.91。 这个比较高。 但是,我们的数据集只有四个对象类别: - -![A screenshot of a cell phone Description automatically generated](img/B14853_12_10.png) - -图 12.5.1:使用 mIoU 进行测试数据集训练期间的语义分割表现 - -第二个指标是平均像素精度。 这类似于在分类器预测上计算准确率的方式。 不同之处在于,分割网络具有的预测数量等于图像中的像素数量,而不是具有一个预测。 对于每个测试输入图像,计算平均像素精度。 然后,计算所有测试图像的平均值。 - -“图 12.5.2”显示了在每个周期使用平均像素精度的语义分割网络的表现。 最大平均像素精度为 97.9%。 我们可以看到平均像素精度与 mIoU 之间的相关性: - -![A screenshot of a cell phone Description automatically generated](img/B14853_12_11.png) - -图 12.5.2:使用测试数据集的平均像素精度在训练期间的语义分割表现 - -“图 12.5.3”显示了输入图像,地面实况语义分割掩码和预测的语义分割掩码的样本: - -![A picture containing indoor, table, bottle Description automatically generated](img/B14853_12_12.png) - -![](img/B14853_12_13.png) - -![](img/B14853_12_14.png) - -图 12.5.3:样本输入,基本事实和语义细分的预测。 我们将黑色分配为背景类,而不是紫色,如先前所用 - -总体而言,我们基于 FCN 并经过 PSPNet 的思想改进的语义分割网络的表现相对较好。 我们的语义分割网络绝不是最优化的。 可以减少特征金字塔中的过滤器数量,以最大程度地减少参数的数量,该参数约为 1110 万。 探索增加特征金字塔中的级别数也很有趣。 读者可以通过执行以下命令来运行验证: - -```py -python3 fcn-12.3.1.py --evaluate ---restore-weights=ResNet56v2-3layer-drinks-best-iou.h5 -``` - -在下一章中,我们将介绍无监督的学习算法。 考虑到监督学习中所需的昂贵且费时的标签,强烈地开发了无监督学习技术。 例如,在本章的语义分割数据集中,一个人花了大约 4 天的手工标签。 如果深度学习始终需要人工标记,那么它就不会前进。 - -# 6\. 总结 - -在本章中,讨论了分割的概念。 我们了解到细分有不同类别。 每个都有自己的目标应用。 本章重点介绍语义分段的网络设计,实现和验证。 - -我们的语义分割网络受到 FCN 的启发,FCN 已成为许多现代,最先进的分割算法(例如 Mask-R-CNN [5])的基础。 PSPNet 的构想进一步增强了我们的网络,该构想在 ImageNet 2016 解析挑战赛中获得第一名。 - -使用 VIA 标记工具,使用与“第 11 章”,“对象检测”中使用的相同图像集生成用于语义分割的新数据集标签。 分割蒙版标记属于同一对象类的所有像素。 - -我们使用平均 IoU 和平均像素准确率指标对语义分割网络进行了训练和验证。 测试数据集上的表现表明,它可以有效地对测试图像中的像素进行分类。 - -如本章最后一部分所述,由于所涉及的成本和时间,深度学习领域正在意识到监督学习的局限性。 下一章重点介绍无监督学习。 它利用了通信领域信息理论中使用的互信息概念。 - -# 7\. 参考 - -1. `Kirillov, Alexander, et al.: Panoptic Segmentation. Proceedings of the IEEE conference on computer vision and pattern recognition. 2019.` -1. `Long, Jonathan, Evan Shelhamer, and Trevor Darrell: Fully Convolutional Networks for Semantic Segmentation. Proceedings of the IEEE conference on computer vision and pattern recognition. 2015.` -1. `Zhao, Hengshuang, et al.: Pyramid Scene Parsing Network. Proceedings of the IEEE conference on computer vision and pattern recognition. 2017.` -1. `Dutta, et al.: VGG Image Annotator http://www.robots.ox.ac.uk/~vgg/software/via/` -1. `He Kaiming, et al.: Mask R-CNN. Proceedings of the IEEE international conference on computer vision. 2017.` \ No newline at end of file diff --git a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/13.md b/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/13.md deleted file mode 100644 index 7836612f..00000000 --- a/机器学习/ApacheCN/apachecn-dl-zh/adv-dl-tf2-keras/13.md +++ /dev/null @@ -1,1240 +0,0 @@ -# 十三、使用互信息的无监督学习 - -许多机器学习任务(例如分类,检测和分段)都依赖于标记的数据。 网络在这些任务上的表现直接受到标记质量和数据量的影响。 问题在于产生足够数量的高质量标注数据既昂贵又费时。 - -为了继续机器学习的发展,新算法应减少对人类标签的依赖。 理想情况下,网络应该从无标签数据中学习,由于互联网的发展以及诸如智能手机和**物联网**(**IoT**)。 从未标记的数据中学习是无监督学习的领域。 在某些情况下,无监督学习也称为自我监督学习,以强调使用纯净的未标记数据进行训练和缺乏人工监督。 在本文中,我们将使用术语无监督学习。 - -在机器学习中,有一些方法可以从未标记的数据中学习。 可以使用深度神经网络和无监督学习中的新思想来改善这些方法的表现。 当处理高度非结构化的数据(例如文本,图像,音频和视频)时,尤其如此。 - -在无监督学习中成功的方法之一是最大化给定神经网络中两个随机变量之间的互信息。 在信息论领域,**互信息**(**MI**)是两个随机变量之间依存性的量度。 - -MI 最近已成功地从未标记的数据中提取了有用的信息,可以帮助学习下游任务。 例如,MI 能够对潜在代码向量进行聚类,从而使分类任务成为简单的线性分离问题。 - -总之,本章的目的是介绍: - -* 互信息的概念 -* 使用神经网络估计 MI -* 下游任务的离散和连续随机变量上的 MI 最大化 -* Keras 中 MI 估计网络的实现 - -我们将从介绍互信息的概念开始。 - -# 1\. 互信息 - -互信息是对两个随机变量`X`和`Y`之间依赖性的度量。 有时,MI 也定义为通过观察`Y`得出的有关`X`的信息量。 MI 也被称为信息获取或观察`Y`时`X`不确定性的降低。 - -与相关性相反,MI 可以测量`X`和`Y`之间的非线性统计依赖性。 在深度学习中,MI 是一种合适的方法,因为大多数现实世界中的数据都是非结构化的,并且输入和输出之间的依赖关系通常是非线性的。 在深度学习中,最终目标是对输入数据和预先训练的模型执行特定任务,例如分类,翻译,回归或检测。 这些任务也称为下游任务。 - -由于 MI 可以发现输入,中间特征,表示和输出中的相关性的重要方面,这些方面本身就是随机变量,因此共享信息通常可以提高下游任务中模型的表现。 - -在数学上,两个随机变量`X`和`Y`之间的 MI 可以定义为: - -![](img/B14853_13_001.png) (Equation 13.1.1) - -哪里: - -* `P`(`X`,`Y`)是 X 和 Y 在样本空间`X`x`Y`上的联合分布 。 -* `P`(`X`)`P`(`Y`)是边际分布`P`(`X`)和`P`(`Y`)分别位于样本空间`X`和`Y`上。 - -换句话说,MI 是联合分布与边际分布乘积之间的 **Kullback-Leibler**(**KL**)散度。 回顾“第 5 章”,“改进的 GAN” ,KL 是两个分布之间距离的度量。 在 MI 的上下文中,KL 距离越大,两个随机变量`X`和`Y`之间的 MI 越高。 通过扩展,MI 越高,`X`对`Y`的依赖性越高。 - -由于 MI 等于边际分布的联合与乘积之间的 KL 散度,因此它暗示它大于或等于零:`I(X; Y) > 0`。 当`X`和`Y`是独立随机变量时,MI 完全等于零。 当`X`和`Y`是独立的时,观察一个随机变量(例如`Y`)不会提供关于另一个随机变量的信息(例如`X`)。 因此,MI 是`X`和`Y`独立程度的度量。 - -如果`X`和`Y`是**离散随机变量**,则通过扩展 KL 散度,MI 可以计算为: - -![](img/B14853_13_003.png) (Equation 13.1.2) - -哪里: - -* `P`(`X`,`Y`)是联合**概率质量函数**(**PMF**)。 -* `P`(`X`)和`P`(`Y`)是边际 PMF。 - -如果联合和边际分布已知,则 MI 可以进行精确计算。 - -如果`X`和`Y`是**连续随机变量**,则通过扩展 KL 散度,MI 可以表示为: - -![](img/B14853_13_004.png) (Equation 13.1.3) - -哪里: - -* `p`(`x`,`y`)是联合**概率密度函数**(**PDF**)。 -* `p`(`x`)和`p`(`y`)是边缘 PDF。 - -连续随机变量的 MI 通常很难处理,并且可以通过变分方法进行估计。 在本章中,我们将讨论估计两个连续随机变量之间的 MI 的技术。 - -在讨论用于计算互信息的技术之前,让我们首先解释一下 MI 与熵之间的关系。 熵在“第 6 章”,“纠缠表示 GAN”中非正式引入,并在 InfoGAN 中得到了应用。 - -# 2\. 互信息和熵 - -MI 也可以用熵来解释。 回想一下“第 6 章”,“纠缠表示 GAN” ,熵`H`(`X`)是对预期信息量的度量。 随机变量`X`的: - -![](img/B14853_13_005.png) (Equation 13.2.1) - -“公式 13.2.1”表示熵还是不确定性的量度。 不确定事件的发生给我们带来了更多的惊喜或信息。 例如,有关员工意外晋升的新闻具有大量信息或熵。 - -使用“公式 13.2.1”,MI 可以表示为: - -![](img/B14853_13_006.png) - -![](img/B14853_13_007.png) - -![](img/B14853_13_008.png) - -![](img/B14853_13_009.png) (Equation 13.2.2) - -“公式 13.2.2”表示 MI 随着边际熵增加而增加,但随联合熵而减少。 就熵而言,MI 的一个更常见的表达式如下: - -![](img/B14853_13_010.png) - -![](img/B14853_13_011.png) - -![](img/B14853_13_012.png) (Equation 13.2.3) - -“公式 13.2.3”告诉我们,MI 随随机变量的熵增加而减小,而随另一个随机变量的条件熵而减小。 或者,如果我们知道`Y`,则 MI 是的信息减少量或`X`的不确定性。 - -等效地, - -![](img/B14853_13_013.png) - -![](img/B14853_13_014.png) (Equation 13.2.4) - -“公式 13.2.4”表示 MI 是对称的: - -![](img/B14853_13_015.png) (Equation 13.2.5) - -MI 也可以用`X`和`Y`的条件熵表示: - -![](img/B14853_13_016.png) (Equation 13.2.6) - -使用贝叶斯定理: - -![](img/B14853_13_017.png) - -![](img/B14853_13_018.png) - -![](img/B14853_13_019.png) - -![](img/B14853_13_020.png) (Equation 13.2.7) - -“图 13.2.1”总结了到目前为止我们讨论的 MI 与条件熵和边际熵之间的所有关系: - -![MI_Venn_Diagram.png](img/B14853_13_01.png) - -图 13.2.1 维恩图显示了 MI 与条件熵和边际熵之间的关系 - -MI 的另一种有趣解释是根据“公式 13.2.3”,可以将其重写为: - -![](img/B14853_13_021.png) (Equation 13.2.8) - -由于`H(X | Y)`是观察到`Y`时的`X`的不确定性,因此“公式 13.2.8”告诉我们, 如果可以最大化 MI,则可以确定`X`给定`Y`。 在“图 13.2.1”中,新月形`H(X | Y)`的面积随着代表 MI 的圆之间的交点增加而减小。 - -再举一个的具体例子,假设`X`是一个随机变量,表示观察到在给定随机字节中的 0 到 255 之间的数字。 假设分布均匀,则转换为`P(X) = 1/256`的概率。 以 2 为底的`X`的熵为: - -![](img/B14853_13_023.png) - -假设随机变量`Y`代表随机字节的 4 个最高有效位。 如果我们观察到 4 个最高有效位全为零,则数字 0 到 15 包含`P(X) = 1/16`,其余数字具有`P(X) = 0`。条件熵在基数 2 中是: - -![](img/B14853_13_025.png) - -这为我们提供了`I(X; Y) = 8 - 4 = 4`的 MI。 注意,随机变量`X`的不确定性或预期信息量在知道`Y`后降低。`X`和`Y`共享的互信息为 4,这也等于两个随机变量共享的位数。“图 13.2.2”说明了两种情况,其中所有位都是随机的,而四个最高有效位都为 0。 - -![A close up of a logo Description automatically generated](img/B14853_13_02.png) - -图 13.2.2 当所有位未知时与某些位已知时的熵 - -鉴于我们已经对 MI 和熵有了很好的了解,我们现在可以将此概念用作无监督学习的一种方法。 - -# 3\. 通过最大化离散随机变量的互信息来进行无监督学习 - -深度学习中的经典问题是监督分类。 在“第 1 章”,“Keras 简介”和“第 2 章”,“深度神经网络”中,我们了解到,在监督分类下,我们需要标记输入图像。 我们对 MNIST 和 CIFAR10 数据集都进行了分类。 对于 MNIST,三层 CNN 和密集层可实现高达 99.3% 的精度。 对于使用 ResNet 或 DenseNet 的 CIFAR10,我们可以实现大约 93% 至 94% 的精度。 MNIST 和 CIFAR10 都被标记为数据集。 - -与监督学习不同,本章的目标是执行无监督学习。 我们的重点是没有标签的分类。 这个想法是,如果我们学习如何对所有训练数据的潜在代码向量进行聚类,那么线性分离算法可以对每个测试输入数据潜在向量进行分类。 - -为了学习没有标签的潜在代码向量的聚类,我们的训练目标是在输入图像`X`和其潜在代码`Y`之间最大化 MI。`X`和`Y`都是随机变量。 这个想法是外观*相似的*图像将具有聚集到相同区域的潜在向量。 线性分配问题可以很容易地将彼此远离的区域分开。 因此,可以以无监督的方式完成分类问题。 数学上,目标是最大化: - -![](img/B14853_13_027.png) (Equation 13.2.3) - -直观地,一旦我们观察到`Y`,我们对`X`充满信心。 “公式 13.2.3”的问题在于,我们无法很好地估计要测量的密度`P(X | Y) H(X | Y)`。 - -Ji 等人的**不变信息聚类**(**IIC**)[1] 建议从联合和边际分布直接测量`I(X; Y)`。 目的是使用“公式 13.1.2”测量引用同一输入的两个潜在代码随机变量之间的 MI。 假设输入`X`编码为`Z`: - -![](img/B14853_13_029.png) - -将相同的输入`X`转换为`X_bar = G(X)`,以便`X`仍可清晰地归类为与`X`相同的类别。 在图像处理中,`G`可以是常见的操作,例如小旋转,随机裁剪和剪切。 有时,只要结果图像的含义相同,就可以接受诸如对比度和亮度调整,边缘检测,少量噪声添加以及归一化之类的操作。 例如,如果`X`是狗的图像,则在`G`之后,`X_bar`显然仍是狗。 - -使用相同编码器网络的潜在代码向量为: - -![](img/B14853_13_035.png) - -因此,我们可以用两个随机变量`Z`和`Z_bar`将“公式 13.1.2”重写为: - -![](img/B14853_13_037.png) (Equation 13.3.1) - -其中`P(Z)`和`P(Z_bar)`可以解释为`Z`和`Z_bar`的边际分布。 对于离散随机变量,`Z`和`Z_bar`都是`P(Z)`和`P(Z_bar)`都是分类分布。 我们可以想象,编码器输出是 *softmax* ,其维数等于训练和测试数据分布中的类数`N`。 例如,对于 MNIST,编码器输出是与训练和测试数据集中的 10 位数字相对应的 10 维一热向量。 - -为了确定“公式 13.3.1”中的每个项,我们首先估计`P(Z, Z_bar)`。 IIC 假设`Z`和`Z_bar`是独立的,因此联合分布可以估计为: - -![](img/B14853_13_045.png) (Equation 13.3.2) - -这将创建一个`N x N`矩阵`P(Z, Z_bar)`,其中每个元素`Z[ij]`对应于同时观察两个随机变量`(Z[i], Z_bar[j])`的概率。 如果对大批量进行此估计,则大样本均值将估计联合概率。 - -由于我们将使用 MI 来估计密度函数,因此 IIC 将采样限制为`(Z[i], Z_bar[i])`。 本质上,对于每个样本`x[i]`,我们计算其潜在代码`P(Z[i]) = E(X[i])`。 然后,我们将`x[i]`转换,并计算其潜在代码`P(Z_bar[i]) = E(X_bar[i])`。 联合分布计算如下: - -![](img/B14853_13_051.png) (Equation 13.3.3) - -其中`M`是批量大小。 由于我们对`x[i]`和`x_bar[i]`使用相同的编码器`E`,因此联合分布应该对称。 我们通过执行以下命令来增强对称性: - -![](img/B14853_13_054.png) (Equation 13.3.4) - -给定`P(Z, Z_bar)`,边际分布可以计算为: - -![](img/B14853_13_056.png) (Equation 13.3.5) - -我们按行求和矩阵的所有条目。 类似地: - -![](img/B14853_13_057.png) (Equation 13.3.6) - -我们按矩阵汇总矩阵的所有条目。 - -给定“公式 13.3.1”中的所有项,我们可以训练神经网络编码器`E`,该编码器使用损失函数来最大化 MI 或最小化负 MI: - -![](img/B14853_13_059.png) (Equation 13.3.7) - -在实现无监督聚类之前,让我们再次反思目标–最大化`I(Z; Z_bar)`。 由于`X`和`X_bar = G(X)`及其对应的潜在代码向量`Z`和`Z_bar`共享相同的信息,因此神经网络编码器`E`应该学习映射`X`和`X_bar`成为潜在向量`Z`和`Z_bar`,它们具有几乎相同的值以最大化其 MI。 在 MNIST 的背景下,看起来相似的数字将具有潜在代码向量,它们聚集在空间的同一区域中。 - -如果潜在代码向量是 *softmax* 的输出,则表明我们正在执行无监督聚类,可以使用线性分配算法将其转换为分类器。 在本章中,我们将介绍两种可能的线性分配算法,这些算法可用于将无监督的聚类转换为无监督的分类。 - -在下一节中,我们将讨论可用于实现无监督聚类的编码器网络模型。 特别是,我们将介绍可用于估计`P(Z)`和`P(Z_bar)`的编码器网络。 - -# 4\. 用于无监督聚类的编码器网络 - -图 13.4.1 中显示了用于无监督聚类的编码器网络实现。 它是一种编码器,具有类似 VGG 的[2]主干和`Dense`层,并具有 *softmax* 输出。 最简单的 VGG-11 具有主干,如“图 13.4.2”所示。 - -对于 MNIST,使用最简单的 VGG-11 骨干将特征映射大小从`MaxPooling2D`操作的 5 倍减至零。 因此,当在 Keras 中实现时,将使用按比例缩小的 VGG-11 主干版本,如图“图 13.4.3”所示。 使用同一组过滤器。 - -![A close up of a logo Description automatically generated](img/B14853_13_03.png) - -图 13.4.1 IIC 编码器网络`E`的网络实现。 输入的 MNIST 图像被中心裁剪为`24 x 24`像素。 在此示例中,`X_bar = G(X)`是随机的`24 x 24`像素裁剪操作。 - -![](img/B14853_13_04.png) - -图 13.4.2 VGG-11 分类器主干 - -在“图 13.4.3”中,有 4 个`Conv2D-BN-ReLU Activation-MaxPooling2D`层,其过滤器大小为`(64, 128, 256, 512)`。 最后的`Conv2D`层不使用`MaxPooling2D`。 因此,最后的`Conv2D`层针对`24 x 24 x 1`裁剪的 MNIST 输入输出`(3, 3, 512)`特征映射。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_13_05.png) - -图 13.4.3 缩小的 VGG 用作编码器主干 - -“图 13.4.4”显示了“图 13.4.1”的 Keras 模型图。 为了提高性能,IIC 执行了超集群。 两个或更多编码器用于生成两个或更多个边际分布`P(Z)`和`P(Z_bar)`。 生成相应的联合分布。 就网络模型的而言,这是由具有两个或更多头的编码器实现的。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_13_06.png) - -图 13.4.4 Keras 中 IIC 编码器`E`的网络实现 - -“图 13.4.4”是单头编码器,而“图 13.4.5”是双头编码器。 请注意,两个头共享相同的 VGG 主干。 - -![A screenshot of a cell phone Description automatically generated](img/B14853_13_07.png) - -图 13.4.5 Keras 中的两头编码器网络`E` - -在以下两个部分的中,我们将研究[II]网络模型是如何实现,训练和评估的。 我们还将研究线性分配问题,作为为每个聚类指定标签的工具。 - -# 5\. Keras 中的无监督聚类实现 - -Keras 中用于无监督聚类的网络模型实现在“列表 13.5.1”中显示。 仅显示初始化。 网络超参数存储在`args`中。 VGG 主干对象在初始化期间提供。 给定骨干,模型实际上只是具有 *softmax* 激活的`Dense`层,如`build_model()`方法所示。 有创建多个头的选项。 - -与“第 11 章”,“对象检测”相似,我们实现了`DataGenerator`类以多线程方式有效地提供输入数据。 `DataGenerator`对象生成由输入图像`X`及其变换后的图像`X_bar`组成的所需配对训练输入数据(即,连体输入图像)。 `DataGenerator`类中最关键的方法`__data_generation()`显示在“列表 13.5.2”中。 输入图像`X`从原始输入图像中央裁切。 对于 MNIST,这是`24 x 24`像素中心裁剪。 变换后的输入图像`X_bar`可以随机旋转`±20`范围内的某个角度,也可以从图像的任何部分随机裁剪`16 x 16`、`18 x 18`或`20 x 20`像素,然后将其调整为`24 x 24`像素。 作物尺寸存储在`crop_sizes`列表中。 - -注意,仅输入图像和变换图像在`DataGenerator`对象生成的数据中很重要。 同样,损失函数所需的配对数据沿批量轴连接。 这将使我们能够在单批配对数据中计算损失函数。 - -“列表 13.5.1”:`iic-13.5.1.py`。 显示初始化和模型创建的 IIC 类:IIC 类: - -```py - def __init__(self, - args, - backbone): - """Contains the encoder model, the loss function, - loading of datasets, train and evaluation routines - to implement IIC unsupervised clustering via mutual - information maximization -``` - -```py - Arguments: - args : Command line arguments to indicate choice - of batch size, number of heads, folder to save - weights file, weights file name, etc - backbone (Model): IIC Encoder backbone (eg VGG) - """ - self.args = args - self.backbone = backbone - self._model = None - self.train_gen = DataGenerator(args, siamese=True) - self.n_labels = self.train_gen.n_labels - self.build_model() - self.load_eval_dataset() - self.accuracy = 0 -``` - -```py - def build_model(self): - """Build the n_heads of the IIC model - """ - inputs = Input(shape=self.train_gen.input_shape, name='x') - x = self.backbone(inputs) - x = Flatten()(x) - # number of output heads - outputs = [] - for i in range(self.args.heads): - name = "z_head%d" % i - outputs.append(Dense(self.n_labels, - activation='softmax', - name=name)(x)) - self._model = Model(inputs, outputs, name='encoder') - optimizer = Adam(lr=1e-3) - self._model.compile(optimizer=optimizer, loss=self.mi_loss) -``` - -“列表 13.5.2”:`data_generator.py`。 用于生成成对的输入数据以训练 IIC 编码器的`DataGenerator`类方法: - -```py - def __data_generation(self, start_index, end_index): - """Data generation algorithm. The method generates - a batch of pair of images (original image X and - transformed imaged Xbar). The batch of Siamese - images is used to trained MI-based algorithms: - 1) IIC and 2) MINE (Section 7) -``` - -```py - Arguments: - start_index (int): Given an array of images, - this is the start index to retrieve a batch - end_index (int): Given an array of images, - this is the end index to retrieve a batch - """ -``` - -```py - d = self.crop_size // 2 - crop_sizes = [self.crop_size*2 + i for i in range(0,5,2)] - image_size = self.data.shape[1] - self.crop_size - x = self.data[self.indexes[start_index : end_index]] - y1 = self.label[self.indexes[start_index : end_index]] -``` - -```py - target_shape = (x.shape[0], *self.input_shape) - x1 = np.zeros(target_shape) - if self.siamese: - y2 = y1 - x2 = np.zeros(target_shape) -``` - -```py - for i in range(x1.shape[0]): - image = x[i] - x1[i] = image[d: image_size + d, d: image_size + d] - if self.siamese: - rotate = np.random.randint(0, 2) - # 50-50% chance of crop or rotate - if rotate == 1: - shape = target_shape[1:] - x2[i] = self.random_rotate(image, - target_shape=shape) - else: - x2[i] = self.random_crop(image, - target_shape[1:], - crop_sizes) -``` - -```py - # for IIC, we are mostly interested in paired images - # X and Xbar = G(X) - if self.siamese: - # If MINE Algorithm is chosen, use this to generate - # the training data (see Section 9) - if self.mine: - y = np.concatenate([y1, y2], axis=0) - m1 = np.copy(x1) - m2 = np.copy(x2) - np.random.shuffle(m2) - x1 = np.concatenate((x1, m1), axis=0) - x2 = np.concatenate((x2, m2), axis=0) - x = (x1, x2) - return x, y -``` - -```py - x_train = np.concatenate([x1, x2], axis=0) - y_train = np.concatenate([y1, y2], axis=0) - y = [] - for i in range(self.args.heads): - y.append(y_train) - return x_train, y -``` - -```py - return x1, y1 -``` - -为了实现 VGG 骨干,在 Keras 中实现了`VGG`类,如“列表 13.5.3”所示。 `VGG`类的灵活性在于可以用不同的方式(或 VGG 的不同样式)进行配置。 显示了用于 IIC VGG 主干配置`cfg`的选项'F'。 我们使用一个辅助函数来生成`Conv2D-BN-ReLU-MaxPooling2D`层。 - -“列表 13.5.3”:`vgg.py`。 - -Keras 中的`VGG backbone`类方法: - -```py -cfg = { - 'F': [64, 'M', 128, 'M', 256, 'M', 512], -} -``` - -```py -class VGG: - def __init__(self, cfg, input_shape=(24, 24, 1)): - """VGG network model creator to be used as backbone - feature extractor -``` - -```py - Arguments: - cfg (dict): Summarizes the network configuration - input_shape (list): Input image dims - """ - self.cfg = cfg - self.input_shape = input_shape - self._model = None - self.build_model() -``` - -```py - def build_model(self): - """Model builder uses a helper function - make_layers to read the config dict and - create a VGG network model - """ - inputs = Input(shape=self.input_shape, name='x') - x = VGG.make_layers(self.cfg, inputs) - self._model = Model(inputs, x, name='VGG') -``` - -```py - @property - def model(self): - return self._model -``` - -```py - @staticmethod - def make_layers(cfg, - inputs, - batch_norm=True, - in_channels=1): - """Helper function to ease the creation of VGG - network model -``` - -```py - Arguments: - cfg (dict): Summarizes the network layer - configuration - inputs (tensor): Input from previous layer - batch_norm (Bool): Whether to use batch norm - between Conv2D and ReLU - in_channel (int): Number of input channels - """ - x = inputs - for layer in cfg: - if layer == 'M': - x = MaxPooling2D()(x) - elif layer == 'A': - x = AveragePooling2D(pool_size=3)(x) - else: - x = Conv2D(layer, - kernel_size=3, - padding='same', - kernel_initializer='he_normal' - )(x) - if batch_norm: - x = BatchNormalization()(x) - x = Activation('relu')(x) -``` - -```py - return x -``` - -回到`IIC`类,`IIC`的关键算法是使负 MI 最小的损失函数。 此方法显示在“列表 13.5.4”中。 为了评估单个批量中的损失,我们研究了`y_pred`,并将其分为上下两半,分别对应于输入图像`X`及其变换后的图像`X_bar`的编码器输出的。 回想一下,配对数据是通过将一批图像`X`和一批其变换后的图像`X_bar`连接在一起而制成的。 - -`y_pred`的下半部分为`Z`,而上半部分为`Z_bar`遵循“公式 10.3.2”至“公式 10.3.7”,联合分布`P(Z, Z_bar)`和边际分布被计算。 最后,返回负数 MI。 注意,每个头对总损失函数的贡献均等。 因此,损失是根据头部的数量来缩放的。 - -“列表 13.5.4”:`iic-13.5.1.py`。 - -Keras 中的`IIC`类损失函数。 损失函数使负 MI 最小化(即,使 MI 最大化): - -```py - def mi_loss(self, y_true, y_pred): - """Mutual information loss computed from the joint - distribution matrix and the marginals -``` - -```py - Arguments: - y_true (tensor): Not used since this is - unsupervised learning - y_pred (tensor): stack of softmax predictions for - the Siamese latent vectors (Z and Zbar) - """ - size = self.args.batch_size - n_labels = y_pred.shape[-1] - # lower half is Z - Z = y_pred[0: size, :] - Z = K.expand_dims(Z, axis=2) - # upper half is Zbar - Zbar = y_pred[size: y_pred.shape[0], :] - Zbar = K.expand_dims(Zbar, axis=1) - # compute joint distribution (Eq 10.3.2 & .3) - P = K.batch_dot(Z, Zbar) - P = K.sum(P, axis=0) - # enforce symmetric joint distribution (Eq 10.3.4) - P = (P + K.transpose(P)) / 2.0 - # normalization of total probability to 1.0 - P = P / K.sum(P) - # marginal distributions (Eq 10.3.5 & .6) - Pi = K.expand_dims(K.sum(P, axis=1), axis=1) - Pj = K.expand_dims(K.sum(P, axis=0), axis=0) - Pi = K.repeat_elements(Pi, rep=n_labels, axis=1) - Pj = K.repeat_elements(Pj, rep=n_labels, axis=0) - P = K.clip(P, K.epsilon(), np.finfo(float).max) - Pi = K.clip(Pi, K.epsilon(), np.finfo(float).max) - Pj = K.clip(Pj, K.epsilon(), np.finfo(float).max) - # negative MI loss (Eq 10.3.7) - neg_mi = K.sum((P * (K.log(Pi) + K.log(Pj) - K.log(P)))) - # each head contribute 1/n_heads to the total loss - return neg_mi/self.args.heads -``` - -IIC 网络训练方法显示在“列表 13.5.5”中。 由于我们使用的是从`Sequence`类派生的`DataGenerator`对象,因此可以使用 Keras `fit_generator()`方法来训练模型。 - -我们使用学习率调度器,每 400 个周期将学习率降低 80%。 `AccuracyCallback`调用`eval()`方法,因此我们可以在每个周期之后记录网络的表现。 - -可以选择保存表现最佳的模型的权重。 在`eval()`方法中,我们使用线性分类器为每个聚类分配标签。 线性分类器`unsupervised_labels()`是一种匈牙利算法,它以最小的成本将标签分配给群集。 - -最后一步将无监督的聚类转换为无监督的分类。 `unsupervised_labels()`函数在“列表 13.5.6”中显示。 - -“列表 13.5.5”:`iic-13.5.1.py`。 - -IIC 网络训练和评估: - -```py - def train(self): - """Train function uses the data generator, - accuracy computation, and learning rate - scheduler callbacks - """ - accuracy = AccuracyCallback(self) - lr_scheduler = LearningRateScheduler(lr_schedule, - verbose=1) - callbacks = [accuracy, lr_scheduler] - self._model.fit_generator(generator=self.train_gen, - use_multiprocessing=True, - epochs=self.args.epochs, - callbacks=callbacks, - workers=4, - shuffle=True) -``` - -```py - def eval(self): - """Evaluate the accuracy of the current model weights - """ - y_pred = self._model.predict(self.x_test) - print("") - # accuracy per head - for head in range(self.args.heads): - if self.args.heads == 1: - y_head = y_pred - else: - y_head = y_pred[head] - y_head = np.argmax(y_head, axis=1) - accuracy = unsupervised_labels(list(self.y_test), - list(y_head), - self.n_labels, - self.n_labels) - info = "Head %d accuracy: %0.2f%%" - if self.accuracy > 0: - info += ", Old best accuracy: %0.2f%%" - data = (head, accuracy, self.accuracy) - else: - data = (head, accuracy) - print(info % data) - # if accuracy improves during training, - # save the model weights on a file - if accuracy > self.accuracy \ - and self.args.save_weights is not None: - self.accuracy = accuracy - folder = self.args.save_dir - os.makedirs(folder, exist_ok=True) - path = os.path.join(folder, self.args.save_weights) - print("Saving weights... ", path) - self._model.save_weights(path) -``` - -“列表 13.5.6”:`utils.py`。 - -匈牙利语算法将标签分配给具有最低成本的集群: - -```py -from scipy.optimize import linear_sum_assignment -def unsupervised_labels(y, yp, n_classes, n_clusters): - """Linear assignment algorithm - - Arguments: - y (tensor): Ground truth labels - yp (tensor): Predicted clusters - n_classes (int): Number of classes - n_clusters (int): Number of clusters - """ - assert n_classes == n_clusters - - # initialize count matrix - C = np.zeros([n_clusters, n_classes]) - - # populate count matrix - for i in range(len(y)): - C[int(yp[i]), int(y[i])] += 1 - - # optimal permutation using Hungarian Algo - # the higher the count, the lower the cost - # so we use -C for linear assignment - row, col = linear_sum_assignment(-C) - - # compute accuracy - accuracy = C[row, col].sum() / C.sum() - - return accuracy * 100 -``` - -![A close up of a logo Description automatically generated](img/B14853_13_08.png) - -图 13.5.1 在三个群集的简单场景中说明的线性分配算法,可以将其最佳地分配给三个类别 - -如图“图 13.5.1”所示,线性分配问题最好使用将三个群集分配给三个类别的简化方案来解释。 线性分配问题找到了类对类的一对一分配,从而使总成本最小。 在“图 13.5.1*”的左侧,显示了聚类结果和真实情况标签。 - -线性分配问题可以找到每个群集的类或类别,或者如何为每个群集分配标签。 还显示了成本矩阵`C`。 对于每个聚类-真实情况对,成本矩阵像元递减 1。该像元的行-列索引是聚类编号-真实情况标签索引。 使用成本矩阵,线性分配问题的工作是找到导致总成本最小的最优矩阵`X`: - -![](img/B14853_13_080.png) (Equation 13.5.1) - -其中`c[ij]`和`x[ij]`分别是矩阵`C`和`X`的元素 。`i`和`j`是索引。`X`的元素受的以下约束: - -`x[ij] ∈ {0, 1}` - -`Σ[j] x[ij] = 1`对于`i = 1, 2, ..., N` - -`Σ[i] x[ij] = 1`对于`j = 1, 2, ..., N` - -`X`是一个二进制矩阵。 每行仅分配给一列。 因此,线性分配问题是组合问题。 最佳解决方案的详细信息超出了本书的范围,此处不再讨论。 - -最佳权重矩阵`X`显示在“图 13.5.1”中。 群集 0 被分配了标签 1。群集 1 被分配了标签 2。群集 2 被分配了标签 0。这可以从成本矩阵中直观地进行验证,因为这导致最低成本为 -4,同时确保每行仅分配给一列。 - -使用此矩阵,群集类的分配显示在最右边的表中。 使用群集类分配时,第四行上只有一个错误。 结果精度为五分之四,即 80%。 - -我们可以将的线性分配问题扩展到为 10 个 MNIST 集群分配标签的问题。 我们在`scipy`包中使用`linear_sum_assignment()`函数。 该函数基于匈牙利算法。“列表 13.5.6”显示了群集标记过程的实现。 有关`linear_sum_assignment()`函数的更多详细信息,请参见[这里](https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linear_sum_assignment.html)。 - -要训​​练 1 头情况下的 IIC 模型,请执行: - -```py -python3 iic-13.5.1.py --heads=1 --train --save-weights=head1.h5 -``` - -对于其他数量的打印头,应相应地修改选项`--heads`和`--save-weights`。 在下一部分中,我们将检查 IIC 作为 MNIST 分类器的表现。 - -# 6\. 将 MNIST 用于验证 - -在本节中,我们将研究使用 MNIST 测试数据集对 IIC 进行验证之后的结果。 在测试数据集上运行聚类预测后,线性分配问题为每个聚类分配标签,从本质上将聚类转换为分类。 我们计算了分类精度,如“表 13.6.1”所示。 IIC 的准确率高于论文中报告的 99.3%。 但是,应该注意的是,并非每次训练都会导致高精度分类。 - -有时,由于优化似乎停留在局部最小值中,我们不得不多次运行训练。 此外,在多头 IIC 模型中,对于所有头部,我们都无法获得相同水平的表现。“表 13.6.1”报告了最佳表现的头部。 - -| **头部数** |`1`|`2`|`3`|`4`|`5`| -| --- | --- | --- | --- | --- | --- | -| **精度,%** | 99.49 | 99.47 | 99.54 | 99.52 | 99.53 | - -表 13.6.1 不同头数的 IIC 精度 - -权重在 GitHub 上可用。 例如,要在单头 IIC 上运行验证: - -```py -python3 iic-13.5.1.py --heads=1 --eval --restore-weights=head1-best.h5 -``` - -总之,我们可以看到可以执行无监督分类。 结果实际上比我们在“第 2 章”,“深度神经网络”中检查的监督分类更好。 在以下各节中,我们将把注意力转向对连续随机变量的无监督学习。 - -# 7\. 通过最大化连续随机变量的互信息进行无监督学习 - -在前面的章节中,我们了解到可以很好地估计离散随机变量的 MI。 我们还证明了借助线性分配算法,通过最大化 MI 来执行聚类的网络可以得出准确的分类器。 - -如果 IIC 是离散随机变量 MI 的良好估计者,那么连续随机变量又如何呢? 在本节的中,我们讨论 Belghazi 等人的**互信息网络估计器**(**MINE**)。 [3]作为连续随机变量 MI 的估计量。 - -MINE 在“公式 13.1.1”中提出了 KL 散度的另一种表示形式,以使用神经网络实现 MI 估计器。 在 MINE 中,使用 KL 散度的 **Donsker-Varadhan**(**DV**)表示: - -![](img/B14853_13_085.png) (Equation 13.7.1) - -在函数`T`的整个空间中占据最高位的位置。`T`是从输入空间(例如图像)映射到实数的任意函数。 回想一下,最高被粗略地解释为最大值。 对于`T`,我们可以从`θ ∈ Θ`参数化的函数`T[θ] = X x Y -> R`系列中进行选择。 因此,我们可以用估计 KL 散度的深度神经网络表示`T[θ]`,因此代表`T`。 - -给定作为 MI 的精确(但难处理)表示`I(X; Y)`及其参数化的估计值`I[θ](X; Y)`作为易于处理的下限,我们可以安全地说: - -![](img/B14853_13_091.png) (Equation 13.7.2) - -其中参数化的 MI 估计为: - -![](img/B14853_13_092.png) (Equation 13.7.3) - -`I[θ](X; Y)`也称为神经信息测度。 在第一个期望中,样本`(x, y) ~ P(X, Y)`从联合分布`P`(`X`,`Y`)中获取。 在第二个期望中,样本`x ~ P(X), y ~ P(Y)`来自边际分布`P(X)`和`P(Y)`。 - -“算法 13.7.1”:`MINE`。 - -初始化所有网络参数`θ`。 - -`θ`尚未收敛时,请执行: - -1. 从联合分布`{(x^(1), y^(1)), (x^(2), y^(2)), ..., (x^(b), y^(b))} ~ P(X, Y)`中抽取一个小批量的`b` -2. 从边际分布`{x^(1), x^(2), ..., x^(b)} ~ P(X)`和`{y^(1), y^(2), ..., y^(b)} ~ P(Y)`中抽取一个小批量的`b`。 -3. 评估下界: - - ![](img/B14853_13_101.png) -4. 评估偏差校正后的梯度: - - ![](img/B14853_13_103.png) - -1. 更新网络参数: - - ![](img/B14853_13_102.png) - - 其中`ε`是学习率。 - -“算法 13.7.1”总结了 MINE 算法。 来自边际分布的样本是来自联合分布的样本,另一个变量已删除。 例如,样本`x`只是简单的样本`(x, y)`,变量`y`被丢弃。 在降为变量`y`的值之后,将`x`的样本进行混洗。 对`y`执行相同的采样方法。 为了清楚起见,我们使用符号`x_bar`和`y_bar`从边际分布中识别样本。 - -在下一部分中,在双变量高斯分布的情况下,我们将使用 MINE 算法估计 MI。 我们将展示使用解析方法估计的 MI 和使用 MINE 估计 MI 的方法。 - -# 8\. 估计二元高斯的互信息 - -在本节中,我们将验证 MINE 的二元高斯分布。“图 13.8.1”显示具有均值和协方差的双变量高斯分布: - -![](img/B14853_13_107.png) (Equation 13.8.1) - -![](img/B14853_13_108.png) (Equation 13.8.2) - -![A picture containing text Description automatically generated](img/B14853_13_09.png) - -图 13.8.1 具有均值和协方差的二维高斯分布,如公式 13.8.1 和公式 13.8.2 所示 - -我们的目标是通过近似“公式 13.1.3”来估计 MI。 可以通过获得大量样本(例如 1 百万个)并创建具有大量箱子(例如 100 个箱子)的直方图来进行近似。“列表 13.8.1”显示了使用装仓对二元高斯分布的 MI 进行的手动计算。 - -“列表 13.8.1”:`mine-13.8.1.py`: - -```py -def sample(joint=True, - mean=[0, 0], - cov=[[1, 0.5], [0.5, 1]], - n_data=1000000): - """Helper function to obtain samples - fr a bivariate Gaussian distribution -``` - -```py - Arguments: - joint (Bool): If joint distribution is desired - mean (list): The mean values of the 2D Gaussian - cov (list): The covariance matrix of the 2D Gaussian - n_data (int): Number of samples fr 2D Gaussian - """ - xy = np.random.multivariate_normal(mean=mean, - cov=cov, - size=n_data) -``` - -```py - # samples fr joint distribution - if joint: - return xy - y = np.random.multivariate_normal(mean=mean, - cov=cov, - size=n_data) -``` - -```py - # samples fr marginal distribution - x = xy[:,0].reshape(-1,1) - y = y[:,1].reshape(-1,1) - xy = np.concatenate([x, y], axis=1) - return xy -``` - -```py -def compute_mi(cov_xy=0.5, n_bins=100): - """Analytic computation of MI using binned - 2D Gaussian -``` - -```py - Arguments: - cov_xy (list): Off-diagonal elements of covariance - matrix - n_bins (int): Number of bins to "quantize" the - continuous 2D Gaussian - """ - cov=[[1, cov_xy], [cov_xy, 1]] - data = sample(cov=cov) - # get joint distribution samples - # perform histogram binning - joint, edge = np.histogramdd(data, bins=n_bins) - joint /= joint.sum() - eps = np.finfo(float).eps - joint[joint