Python-Web-深度学习实用指南-全-
Python Web 深度学习实用指南(全)
原文:
annas-archive.org/md5/ba1cfcea7397d362b4cda60b175c7bb2
译者:飞龙
前言
深度学习技术可用于开发智能网页应用。近年来,越来越多的公司将深度学习技术应用于其产品和业务,取得了显著的增长。与此同时,许多创业公司开始提供基于人工智能和深度学习的解决方案,专注于细分问题。本书介绍了用于在网页开发中实现深度学习的众多工具和技术实践,使用的是 Python 语言。
首先,你将学习机器学习的基础知识,重点介绍深度学习和神经网络的基本概念,并讲解它们的常见变种,如卷积神经网络,并介绍如何将它们集成到使用不同标准网页技术栈构建的前端网站中。你将使用 Python 库(如 Django 和 Flask)创建支持深度学习的网页应用,并通过为自定义模型创建 REST API 来实现。你将为深度学习基础的网页部署设置一个云环境,使用 Google Cloud 和 AWS,并获得有关如何使用它们经过验证的深度学习 API 的指导。此外,你将使用微软的智能情感 API,从人脸图片中检测人类情绪。你还将学习如何部署实际的网页,并深入了解如何使用 reCaptcha 和 Cloudflare 来保障网站安全,确保用户体验的稳定性。最后,你将运用自然语言处理技术,从用户评论中推荐餐厅,并通过 Dialogflow 在网页中集成语音用户体验。
在本书结束时,你将能够借助最佳工具和实践,部署智能网页应用和网站。
本书适合人群
本书适合数据科学家、机器学习从业者和深度学习工程师,尤其是那些希望在网页上应用深度学习技术和方法的人。本书也非常适合那些希望在浏览器中使用智能技术使其更具互动性的网页开发人员。通过本书,你将获得有关浏览器数据的深入见解。
具备 Python 编程语言的基础知识,以及基本的机器学习技术(如 Google 提供的机器学习速成课程中所涵盖的内容)将对阅读本书大有帮助。
本书内容概述
第一章,揭开人工智能和机器学习基础的面纱,简要介绍了机器学习、深度学习及与 Web 开发相关的其他人工智能方法论。本章快速回顾了机器学习流程中的基本主题,如探索性数据分析、数据预处理、特征工程、训练与测试、模型评估等。最后,展示了在 AI 普及前后,网站的互动性和用户体验的比较。我们还研究了大公司如何在 Web 上使用 AI,以及 AI 如何革新他们的产品。
第二章,使用 Python 进行深度学习入门,介绍了与深度学习相关的基本概念和术语,并讲解了如何使用深度学习在 Python 中通过不同的深度学习库构建一个简单的 Web 应用程序。
第三章,创建你的第一个深度学习 Web 应用程序,讨论了几个与深度学习相关的 Web 应用程序结构的重要概念。然后,本章继续讨论如何理解数据集的方法。接下来,介绍了如何实现并改进一个简单的神经网络,并讲解了如何将其封装成 API,用于开发一个简单的 Web 应用程序。我们进一步展示了如何使用不同的标准 Web 技术栈来实现这个 API。
第四章,TensorFlow.js 入门,介绍了最流行的深度学习 JavaScript 库——TensorFlow.js(Tf.js)。本章简要概述了 TensorFlow.js 是什么,以及它在浏览器中能够完成的任务。此外,本章展示了如何使用 TensorFlow.js 使用预训练模型,并用它构建一个简单的 Web 应用程序。
第五章,通过 API 进行深度学习,介绍了 API 的概念及其在软件开发中的重要性。接下来,本章展示了不同深度学习 API 的示例。最后,本章介绍了如何选择适合特定用例的深度学习 API 提供商。本章涵盖的深度学习 API 包括视觉 API、文本 API 等。
第六章,在 Google Cloud Platform 上使用 Python 进行深度学习,介绍了 Google Cloud Platform 为 Web 开发者提供的服务,帮助他们将这些服务集成到网站中。重点介绍了 Dialogflow,它可以用来制作聊天机器人和对话式人工智能;Cloud Inference API,它可以用来构建推荐系统;以及 Translation API,它可以为不同地区的用户提供其语言的网页内容。本章详细讨论了这些应用,并展示了如何使用 Python 进行操作的基本教程。
第七章,在 AWS 上使用 Python 进行深度学习:物体检测与家庭自动化,介绍了亚马逊网络服务(AWS),并简要讲解了其各种服务,包括 Alexa API 和 Rekognition API。Alexa API 可用于构建家庭自动化网站应用和其他交互式界面,而 Rekognition API 则可用于检测照片和视频中的人物与物体。
第八章,在 Microsoft Azure 上使用 Python 进行深度学习,介绍了 Microsoft Azure 云服务,重点介绍了 Cognitive Toolkit,这是微软对 TensorFlow 情感 API 的替代品,可以通过分析人脸照片来判断人物情感,以及 Text-to-Speech API,可以将文本转换为自然的语音。
第九章,深度学习驱动的网站通用生产框架,介绍了在生产环境中高效部署深度学习的通用框架。内容涵盖了减少计算资源的策略、将原始数据集转化为用于训练深度学习模型的数据集,以及如何以最小的资源消耗方式使模型在网站上可用。
第十章,使用深度学习保护 Web 应用,讨论了使用 Python 和深度学习技术保护网站的多种技巧和方法。我们介绍了 reCaptcha 和 Cloudflare,并讨论了它们如何增强网站的安全性。还展示了如何在 Python 后端利用深度学习实现安全机制,以检测网站上的恶意用户。
第十一章,DIY – Web 深度学习生产环境,讨论了在生产环境中更新模型的方法,并根据需求选择合适的方法。我们首先进行简要概述,然后展示了一些著名的工具,用于创建深度学习数据流。最后,我们实现了在线学习或增量学习的演示,确立了生产环境中的模型更新方法。
第十二章,使用 DL API 和客户支持聊天机器人创建 E2E Web 应用,介绍了自然语言处理,并讨论了如何使用 Dialogflow 创建一个聊天机器人来解决一般的客户支持查询,并将其集成到 Django 和 Flask 网站中。我们探讨了实现机器人个性的方式,以及如何使这些系统资源更有效。我们还介绍了一种基于文本转语音和语音转文本的用户界面的实现方法,使用的是 Python。
附录,深度学习在 Web 上的成功案例与新兴领域,展示了一些最著名的网站,它们的产品在很大程度上依赖于深度学习的强大功能。本章还讨论了在 Web 开发中可以通过深度学习增强的关键研究领域。这将帮助你更深入地了解 Web 技术与深度学习的融合,并激励你提出自己的智能 Web 应用。
为了充分利用这本书
本书假设读者理解 Python 语言,特别是 Python 3.6 及以上版本。强烈建议在本地系统上安装 Anaconda 发行版的 Python。任何支持 Python 3.6 及以上版本的 Anaconda 发行版都适用于运行本书中的示例。
在硬件方面,本书假设你的计算机上有麦克风、扬声器和摄像头。
本书涵盖的软件/硬件 | 操作系统要求 |
---|---|
Anaconda Python 发行版和其他 Python 包 | 最低 1 GB 内存,推荐 8 GB,15 GB 磁盘空间 |
你选择的代码编辑器(推荐使用 Sublime Text 3) | 2 GB 内存 |
如果你使用的是本书的电子版本,我们建议你自己输入代码或通过 GitHub 仓库访问代码(链接在下一部分提供)。这样可以帮助你避免与复制粘贴代码相关的潜在错误。
本书预计你会自己尝试实现其中的示例。如果遇到问题,你可以通过电子邮件联系作者——Sayak Paul (spsayakpaul@gmail.com
) 和 Anubhav Singh (xprilion@gmail.com
)。如果你无法运行本书代码仓库中的示例代码,可以在仓库中提出问题,我们会在那里回复你!
下载示例代码文件
你可以从你的 www.packt.com 账户下载本书的示例代码文件。如果你在其他地方购买了这本书,可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给你。
你可以按照以下步骤下载代码文件:
-
在 www.packt.com 登录或注册。
-
选择支持选项卡。
-
点击代码下载。
-
在搜索框中输入书名并按照屏幕上的说明操作。
文件下载完成后,请确保使用最新版本的以下工具解压或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书的代码包也托管在 GitHub 上:github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web
。如果代码有更新,GitHub 上的现有代码库也会同步更新。
我们还提供了其他代码包,来自我们丰富的图书和视频目录,您可以在github.com/PacktPublishing/
查看。赶紧去看看吧!
下载彩色图像
我们还提供了一份包含书中截图/图表彩色图像的 PDF 文件。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789956085_ColorImages.pdf
。
使用的约定
本书中使用了多种文本约定。
CodeInText
:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。以下是一个例子:“我们现在需要从模型训练步骤中导入保存的模型和权重。一旦导入,我们需要重新编译模型,并使用make_predict_function()
方法使其具备predict
功能。”
代码块如下所示:
def remove_digits(s: str) -> str:
remove_digits = str.maketrans('', '', digits)
res = s.translate(remove_digits)
return res
任何命令行输入或输出都写作如下:
python main.py
粗体:表示新术语、重要词汇或屏幕上显示的文字。例如,菜单或对话框中的词汇以这种方式显示。以下是一个例子:“填写条目并点击继续。”
警告或重要说明以此方式显示。
提示和技巧以这种方式显示。
联系我们
我们欢迎读者的反馈意见。
一般反馈:如果您对本书的任何部分有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com
联系我们。
勘误:尽管我们已尽力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现错误,我们将不胜感激,如果您能向我们报告。请访问www.packtpub.com/support/errata,选择您的书籍,点击勘误提交表单链接并填写相关信息。
盗版:如果您在互联网上发现任何非法的我们作品副本,请提供相关的地址或网站名称。请通过copyright@packt.com
与我们联系,并提供该材料的链接。
如果你有兴趣成为作者:如果你在某个领域拥有专业知识,并且有兴趣撰写或参与编写一本书,请访问authors.packtpub.com。
评价
请留下评价。在你阅读并使用本书后,为什么不在你购买的站点上留下评论呢?潜在读者可以看到并参考你的公正意见做出购买决策,我们 Packt 可以了解你对我们产品的看法,而我们的作者也能看到你对他们书籍的反馈。谢谢!
如需了解有关 Packt 的更多信息,请访问packt.com。
目录
-
标题页
-
版权和致谢
- Web 的 Python 深度学习实战
-
关于 Packt
- 为什么要订阅?
-
贡献者
-
关于作者
-
关于评论员
-
Packt 正在寻找像你这样的作者
-
-
献辞
-
前言
-
本书的适用对象
-
本书内容
-
如何最大化利用本书
-
下载示例代码文件
-
下载彩色图片
-
使用的约定
-
-
联系我们
- 评价
-
-
网络上的人工智能
-
揭开人工智能和机器学习基础的神秘面纱
-
人工智能简介及其类型
-
推动人工智能的因素
-
数据
-
算法的进展
-
硬件的进展
-
高性能计算的民主化
-
-
-
机器学习 – 最流行的人工智能形式
-
什么是深度学习?
-
人工智能、机器学习和深度学习之间的关系
-
重新审视机器学习的基础
-
机器学习的类型
-
监督学习
-
无监督学习
-
强化学习
-
半监督学习
-
-
必要术语
-
训练集、测试集与验证集
-
偏差与方差
-
过拟合与欠拟合
-
训练误差与泛化误差
-
-
-
标准的机器学习工作流程
-
数据检索
-
数据准备
-
探索性数据分析(EDA)
-
数据处理与清理
-
特征工程与提取/选择
-
-
建模
-
模型训练
-
模型评估
-
模型调优
-
-
模型比较与选择
-
部署与监控
-
-
人工智能前后的网络
-
聊天机器人
-
网页分析
-
垃圾邮件过滤
-
搜索
-
-
最大网络-AI 参与者及其在 AI 方面的应用
-
Google
-
Google 搜索
-
Google 翻译
-
Google 助手
-
其他产品
-
-
Facebook
-
虚假个人资料
-
虚假新闻与令人不安的内容
-
其他用途
-
-
亚马逊
-
Alexa
-
亚马逊机器人
-
DeepLens
-
-
-
总结
-
-
使用深度学习进行网页开发
-
使用 Python 开始深度学习
-
解密神经网络
-
人工神经元
-
线性神经元的结构
-
非线性神经元的结构
-
-
关于神经网络输入和输出层的说明
-
梯度下降与反向传播
-
-
不同类型的神经网络
-
卷积神经网络
-
循环神经网络
-
将字母输入网络
-
初始化权重矩阵及其他
-
将权重矩阵组合起来
-
应用激活函数和最终输出
-
-
-
探索 Jupyter Notebooks
-
安装 Jupyter Notebook
-
使用 pip 安装
-
使用 Anaconda 安装
-
-
验证安装
-
Jupyter Notebooks
-
-
设置基于深度学习的云环境
-
设置 AWS EC2 GPU 深度学习环境
-
步骤 1:创建一个 EC2 GPU 实例
-
步骤 2:SSH 登录到你的 EC2 实例
-
步骤 3:在 GPU 实例上安装 CUDA 驱动
-
步骤 4:安装 Python 的 Anaconda 发行版
-
步骤 5:运行 Jupyter
-
-
在 Crestle 上进行深度学习
-
其他深度学习环境
-
-
探索 NumPy 和 pandas
-
NumPy
-
NumPy 数组
-
基础 NumPy 数组操作
-
NumPy 数组与 Python 列表
-
多行多列的数组切片
-
切片作业
-
-
-
Pandas
-
-
总结
-
-
创建你的第一个深度学习 Web 应用程序
-
技术要求
-
构建深度学习 Web 应用程序
- 一般深度学习 Web 应用程序的结构图
-
理解数据集
-
MNIST 手写数字数据集
-
探索数据集
-
创建读取图像文件的函数
-
创建读取标签文件的函数
-
数据集总结
-
-
-
使用 Python 实现简单的神经网络
-
导入必要的模块
-
重用我们的函数来加载图像和标签文件
-
调整数组的形状以便使用 Keras 进行处理
-
使用 Keras 创建神经网络
-
编译和训练 Keras 神经网络
-
评估和存储模型
-
-
创建一个 Flask API 来与服务器端 Python 交互
-
设置环境
-
上传模型结构和权重
-
创建我们的第一个 Flask 服务器
-
导入必要的模块
-
将数据加载到脚本运行时并设置模型
-
设置应用程序和索引函数
-
转换图像函数
-
预测 API
-
-
通过 cURL 使用 API 并使用 Flask 创建 Web 客户端
-
通过 cURL 使用 API
-
为 API 创建一个简单的 Web 客户端
-
-
改进深度学习后端
-
总结
-
-
开始使用 TensorFlow.js
-
技术要求
-
TF.js 的基础知识
-
什么是 TensorFlow?
-
什么是 TF.js?
-
为什么选择 TF.js?
-
TF.js 的基本概念
-
张量
-
变量
-
运算符
-
模型与层
-
-
使用 TF.js 的案例研究
-
我们的 TF.js 小项目的问题陈述
-
鸢尾花数据集
-
-
-
你的第一个深度学习 Web 应用与 TF.js
-
准备数据集
-
项目架构
-
启动项目
-
创建 TF.js 模型
-
训练 TF.js 模型
-
使用 TF.js 模型进行预测
-
创建简单客户端
-
运行 TF.js Web 应用
-
-
TF.js 的优点与限制
-
总结
-
-
开始使用不同的深度学习 API 进行 Web 开发
-
通过 API 进行深度学习
-
什么是 API?
-
使用 API 的重要性
-
API 与库有何不同?
-
一些广为人知的深度学习 API
-
一些鲜为人知的深度学习 API
-
选择深度学习 API 提供商
-
总结
-
-
在 Google Cloud Platform 上使用 Python 进行深度学习
-
技术要求
-
设置你的 GCP 账户
-
在 GCP 上创建你的第一个项目
-
在 Python 中使用 Dialogflow API
-
创建 Dialogflow 账户
-
创建新智能体
-
创建新意图
-
测试你的智能体
-
安装 Dialogflow Python SDK
-
创建 GCP 服务账户
-
使用 Python API 调用 Dialogflow 智能体
-
-
在 Python 中使用 Cloud Vision API
-
使用预训练模型的重要性
-
设置 Vision Client 库
-
使用 Python 调用 Cloud Vision API
-
-
在 Python 中使用 Cloud Translation API
-
为 Python 设置 Cloud Translate API
-
在 Python 中使用 Google Cloud Translation 库
-
-
总结
-
-
使用 Python 进行 AWS 上的深度学习:目标检测与家庭自动化
-
技术要求
-
在 AWS 上入门
-
AWS 产品简介
-
开始使用 boto3
-
配置环境变量和安装 boto3
-
在 Python 中加载环境变量
-
创建 S3 存储桶
-
使用 boto3 从 Python 代码访问 S3
-
-
在 Python 中使用 Rekognition API
-
在 Python 中使用 Alexa API
-
项目的前提条件和框架图
-
为技能创建配置
-
设置 Amazon 登录
-
创建技能
-
配置 AWS Lambda 函数
-
创建 Lambda 函数
-
配置 Alexa 技能
-
为技能设置 Amazon DynamoDB
-
部署 AWS Lambda 函数代码
-
测试 Lambda 函数
-
测试 AWS 家庭自动化技能
-
-
总结
-
-
在 Microsoft Azure 上使用 Python 进行深度学习
-
技术要求
-
在 Azure 中设置账户
-
Azure 提供的深度学习服务介绍
-
使用 Face API 和 Python 进行目标检测
-
初始设置
-
从 Python 代码中调用 Face API
-
-
使用文本分析 API 和 Python 提取文本信息
- 从 Python 代码中使用文本分析 API
-
CNTK 简介
-
开始使用 CNTK
-
在本地计算机上安装
-
在 Google Colaboratory 上安装
-
-
创建 CNTK 神经网络模型
-
训练 CNTK 模型
-
测试和保存 CNTK 模型
-
-
Django Web 开发简要介绍
-
开始使用 Django
-
创建一个新的 Django 项目
-
设置主页模板
-
-
从 Django 项目中使用 CNTK 进行预测
-
设置预测路由和视图
-
进行必要的模块导入
-
使用 CNTK 模型加载和预测
-
测试 Web 应用
-
-
总结
-
-
生产环境中的深度学习(智能 Web 应用)
-
深度学习驱动网站的通用生产框架
-
技术要求
-
定义问题陈述
-
建立项目的思维模型
-
避免一开始就得到错误数据的机会
-
-
如何不构建 AI 后端
-
期望网站的 AI 部分是实时的
-
假设来自网站的数据是理想的
-
-
一个端到端 AI 集成 Web 应用示例
-
数据收集与清理
-
构建 AI 模型
-
进行必要的导入
-
读取数据集并准备清理函数
-
提取所需的数据
-
应用文本清理
-
将数据集划分为训练集和测试集
-
聚合关于产品和用户的文本
-
创建用户和产品的 TF-IDF 向量化器
-
根据评分创建用户和产品索引
-
创建矩阵分解函数
-
将模型保存为 pickle
-
-
-
构建接口
-
创建 API 以回答搜索查询
-
创建接口以使用 API
-
-
总结
-
-
使用深度学习保护 Web 应用
-
技术要求
-
reCAPTCHA 的故事
-
恶意用户检测
-
基于 LSTM 的用户认证模型
-
构建认证有效性检查模型
-
托管自定义认证验证模型
-
-
基于 Django 的 API 使用应用
-
Django 项目设置
-
在项目中创建应用
-
将应用链接到项目
-
向网站添加路由
-
在广告牌应用中创建路由处理文件
-
添加认证路由和配置
-
创建登录页面
-
创建登出视图
-
创建登录页面模板
-
广告牌页面模板
-
添加到广告牌页面模板
-
广告牌模型
-
创建广告牌视图
-
创建 账单并添加视图
-
创建管理员用户并进行测试
-
-
在 Python 中使用 reCAPTCHA 进行 Web 应用保护
-
使用 Cloudflare 进行网站安全保护
-
总结
-
-
DIY - Web DL 生产环境
-
技术要求
-
DL 生产方法概览
-
Web API 服务
-
在线学习
-
批量预测
-
Auto ML
-
-
在生产环境中部署 ML 的流行工具
-
creme
-
Airflow
-
AutoML
-
-
实现一个演示的 DL Web 环境
-
构建预测模型
-
步骤 1 — 导入必要的模块
-
步骤 2 — 加载数据集并观察
-
步骤 3 — 分离目标变量
-
步骤 4 — 对特征进行缩放处理
-
步骤 5 — 将数据集拆分为测试集和训练集
-
步骤 6 — 在 sklearn 中创建神经网络对象
-
步骤 7 — 执行训练
-
-
实现前端功能
-
实现后端功能
-
-
将项目部署到 Heroku
-
安全措施、监控技术和性能优化
-
总结
-
-
使用 DL API 和客户支持聊天机器人创建 E2E Web 应用
-
技术要求
-
NLP 简介
-
语料库
-
词性
-
分词
-
词干提取和词形还原
-
词袋模型
-
相似度
-
-
聊天机器人简介
-
创建具有客户支持代表个性的 Dialogflow 聊天机器人
-
开始使用 Dialogflow
-
步骤 1 — 打开 Dialogflow 控制台
-
步骤 2 – 创建新的代理
-
步骤 3 – 理解仪表盘
-
步骤 4 – 创建意图
-
步骤 4.1 – 创建 HelpIntent
-
步骤 4.2 – 创建 CheckOrderStatus 意图
-
-
步骤 5 – 创建 Webhook
-
步骤 6 – 创建 Firebase 云函数
-
步骤 6.1 – 将所需的包添加到 package.json
-
步骤 6.2 – 向 index.js 添加逻辑
-
-
步骤 7 – 为机器人添加个性
-
-
-
使用 ngrok 方便在本地主机上使用 HTTPS API
-
使用 Django 创建测试 UI 以管理订单
-
步骤 1 – 创建 Django 项目
-
步骤 2 – 创建一个使用订单管理系统 API 的应用
-
步骤 3 – 配置 settings.py
-
步骤 3.1 – 将 apiui 应用添加到已安装应用列表
-
步骤 3.2 – 移除数据库设置
-
-
步骤 4 – 向 apiui 添加路由
-
步骤 5 – 在 apiui 应用中添加路由
-
步骤 6 – 创建所需的视图
-
步骤 6.1 – 创建 indexView
-
步骤 6.2 – 创建 viewOrder
-
-
步骤 7 – 创建模板
-
-
使用 Web Speech API 在网页上进行语音识别和语音合成
-
步骤 1 – 创建按钮元素
-
步骤 2 – 初始化 Web Speech API 并进行配置
-
步骤 3 – 调用 Dialogflow 代理
-
步骤 4 – 在 Dialogflow Gateway 上创建 Dialogflow API 代理(由 Ushakov 提供)
-
步骤 4.1 – 在 Dialogflow Gateway 上创建账户
-
步骤 4.2 – 为 Dialogflow 代理项目创建服务账户
-
步骤 4.3 - 将服务密钥文件上传到 Dialogflow Gateway
-
-
步骤 5 - 为按钮添加点击处理程序
-
-
总结
-
-
附录:关于深度学习在网络上的成功案例与新兴领域
-
成功案例
-
Quora
-
Duolingo
-
Spotify
-
Google 搜索/照片
-
-
关键新兴领域
-
音频搜索
-
阅读理解
-
社交媒体上的假新闻检测
-
-
总结
-
-
你可能喜欢的其他书籍
- 留下评论 - 让其他读者知道你的想法
第一章:网络上的人工智能
本节介绍了人工智能(AI)的定义,并展示了人工智能如何在很大程度上影响网络。它还简要讨论了机器学习的基础知识。
本节包含以下章节:
- 第一章,揭开人工智能的神秘面纱与机器学习基础
第二章:解密人工智能与机器学习基础
“正如电力在 100 年前几乎改变了一切,今天我实际上很难想出一个我认为在接下来的几年中不会被人工智能改变的行业。”
- 安德鲁·吴
这句话可能听起来非常熟悉,毋庸置疑,作为一句声明,它在当前的技术颠覆背景下产生了强烈的共鸣。近年来,人工智能(AI)已成为几乎每个行业的重要关注点。无论是教育公司、电信公司,还是在医疗行业工作的组织——所有这些行业都已将人工智能融入其中,以提升其业务。人工智能与多个行业的这种深度融合,随着时间的推移只会变得更好,并以智能的方式解决现实世界中的关键问题。今天,我们的手机可以根据指示为我们预约临床检查,我们的手机摄像头能够告诉我们它们拍摄的图像中的多个由人类感知的属性,我们的汽车报警系统可以检测到我们的驾驶动作,并在可能发生事故时保护我们。这些例子只会越来越好,并随着研究、技术进步和计算能力的普及变得更加智能。
随着我们迈入软件 2.0 时代,理解为什么一项自 1950 年代就已存在的技术如今成为头条新闻显得尤为重要。是的!人工智能诞生于 1950 年代,当时一小部分计算机科学家和数学家,如艾伦·图灵,开始思考机器是否能思考,是否可以赋予它们智能,让它们无需显式编程就能自主回答问题。
在这一思想诞生后不久,约翰·麦卡锡于 1956 年在一次学术会议上首次提出了“人工智能”这一术语。从图灵在其论文《计算机机械与智能》中提出的“机器能思考吗?”(大约在 1950 年)问题,到今天的 21 世纪,人工智能的世界展现了一些前所未见的结果,甚至是我们曾经无法想象的。
今天,几乎不可能想象一个没有使用互联网的日子。它已经轻松成为我们生活中的基本需求之一。我们最喜爱的搜索引擎能够直接回答我们的问题,而不是给出一长串相关的链接。它们可以分析在线文本,理解其意图并总结内容。所有这一切的实现,都是因为人工智能的存在。
本书旨在为读者提供一份实用指南,教读者如何利用深度学习等人工智能技术,基于计算机视觉、自然语言处理、安全性等领域,构建智能化的 Web 应用程序。本章为读者提供了人工智能及其不同类型的快速回顾,并介绍了机器学习的基本概念,同时介绍了行业中的一些大牌公司及其如何将人工智能与 Web 技术融合的案例。我们将覆盖以下内容:
-
人工智能及其不同类型简介
-
机器学习(ML):最受欢迎的人工智能技术
-
深度学习(DL)的简要介绍
-
人工智能、机器学习与深度学习之间的关系
-
机器学习的基本原理
-
人工智能之前和之后的 Web
-
主要的 Web-AI 玩家及其正在进行的工作
人工智能及其类型简介
简单来说,人工智能就是赋予机器执行智能行为的能力。例如,我们许多人都能下棋。从本质上讲,我们通过学习下棋的基本原理,接着我们参与与他人对弈。那么,机器可以做到吗?机器能否独立学习并与我们下棋呢?
人工智能试图通过将我们所谓的智能以某些规则的形式赋予机器来实现这一目标。机器在这里指的是任何能够进行计算的事物。例如,它可以是软件或机器人。
其实,人工智能有多种类型。流行的类型包括:
-
模糊系统
-
专家系统
-
机器学习系统
最后一种类型听起来最为熟悉。我们将在下一节中详细介绍它。但在我们继续之前,现在是回顾一下推动当前人工智能进展的一些关键因素的好时机。
推动人工智能发展的因素
推动人工智能发展的主要因素有以下几种:
-
数据
-
算法的进展
-
计算机硬件的进展
-
高性能计算的普及
数据
当前的数据量庞大——正如 Google 首席经济学家Hal Varian在 2016 年所说:
"自人类文明的黎明到 2003 年,我们仅创造了五个艾字节;而现在,我们每两天就能创造这么多数据。到 2020 年,这一数字预计将达到 53 泽字节(53 万亿吉字节)——增长了 50 倍。"
这可是庞大的数据量。随着数字设备的数量不断增长,这种数据量只会呈指数增长。过去,汽车行驶时仪表盘上仅显示车速。而今天,我们进入了一个时代,汽车的每个部件都可以在每一刻生成日志,帮助我们完全重建汽车生命周期的任何一个瞬间。
一个从生活中学到的东西越多,这个人就会变得越智慧,也就能更好地预测未来事件的结果。类比于机器,软件训练所得到的(优质)数据越多,它在预测未来未见数据时就越准确。
在过去的几年里,由于多种因素,数据的可用性呈指数增长:
-
更便宜的存储
-
更高的数据传输速率
-
基于云的存储解决方案的可用性
-
高级传感器
-
物联网
-
各种数字电子设备形式的增加
-
网站和本地应用程序的使用增加
现在的数字设备比以往任何时候都要多。它们都配备了可以随时生成日志的系统,并通过互联网将这些日志传输到制造它们的公司或任何购买这些数据的其他供应商。此外,许多日志是由人们使用的网站或应用程序创建的。所有这些都可以轻松地存储在基于云的存储解决方案中,或存储在高存储容量的物理存储设备中,而这些现在比以前便宜了。
如果你环顾四周,可能会看到一台你经常使用多个软件和网站的笔记本电脑——这些软件和网站可能都在收集你在上面执行的每个操作的数据。同样,你的手机也是这样一个数据生成设备。通过你的电视机,提供多个频道的电视服务提供商——无论是服务提供商还是频道提供商,都在收集关于你的数据,以便更好地为你服务并改进他们的产品。你只需要想象一下,单个人每天产生的庞大数据量,而在这个星球上有数十亿人!
算法的进步
算法是一组明确无误的步骤,能解决给定问题。随着时间的推移,科学的发展以及人类借助数学理解自然法则的深入,算法得到了改进。自然界常常为复杂问题提供了灵感。神经网络可能是如今最受关注的、自然启发的算法之一。
当计算机逻辑开始使用多个 if-else 阶梯时,没人会想到有一天我们会有计算机程序,能够学习生成类似于 if-else 阶梯的结果,而无需手动编写条件。更重要的是,今天我们有计算机程序,可以生成其他程序,来模拟人工智能!
毫无疑问,随着时间的推移,人类开发的算法,甚至现在由机器开发的算法,正变得越来越智能、越来越强大,能够更好地执行任务。这直接推动了神经网络的兴起,虽然它们的初步形式似乎是一个耗时的超嵌套循环,用于解决矩阵和向量运算问题。
硬件的进步
当英特尔在 1970 年推出首款动态随机存取内存(DRAM)模块时,它能够存储 1KB 的数据。大约 50 年后,市场上已经有了 128GB 的内存模块。这是原来容量的 1.28 x 10⁸倍。
硬盘也展示了类似的趋势。第一款个人计算机硬盘只能存储宝贵的 5 兆字节,而 2016 年,希捷公司宣布推出一款 60TB 的固态硬盘存储。这是 1.2 x 10⁷倍的增长。
但我们只讨论了直接的个人计算比较,并没有考虑自第一台计算机推出以来技术增长的影响。今天,随着云计算的到来,听到人们谈论无限云存储已经变得非常普遍。
人工智能在计算速度和数据存储的指数级增长中受益匪浅。
高性能计算的民主化
随着商品硬件成本的降低和性能的提升,高性能计算如今不再是科技巨头的专属。今天,任何个人都可以非常容易地为自己的个人使用搭建一个计算设备网络,以实现高性能计算,前提是他们不满足于单一设备所能提供的卓越性能。然而,投资硬件并不是获得高性能计算的唯一方式。基于云计算的解决方案的出现,使得通过点击部署方法即可使用非常高速的计算基础设施。用户可以随时在网络上启动云实例,并以最低的费用在其上运行性能密集型的软件。
随着高性能计算的普及,个人开发者手中掌握了开发人工智能解决方案的能力。这促使了人工智能的创意和研究型应用数量的激增。
让我们现在揭开当前最流行的人工智能形式,并讨论一些关于它的重要概念。
机器学习 – 最流行的人工智能形式
不考虑任何数学符号或过多的理论细节,让我们从直观的角度尝试理解机器学习(ML)这一术语。为此,我们需要看看我们是如何实际学习的。你还记得在学校时,我们被教导如何识别句子中的词性吗?我们被提供了一套规则来识别句子中的词性。我们得到了许多例子,最初我们的老师会为我们识别句子中的词性,以便有效地训练我们,使我们能够利用这种学习经验来识别那些没有教给我们的句子中的词性。此外,这个学习过程本质上适用于我们所学的任何内容。
如果我们也能以类似的方式训练机器呢?如果我们能以某种方式编程,让它们能从经验中学习,并且基于这些知识开始回答问题呢?嗯,这已经实现了,而无论我们是否意识到,我们都在享受它带来的好处。这正是直观理解下的机器学习(ML)。为了更正式、标准的理解,我们来看一下汤姆·米切尔(Tom Mitchell)在《机器学习》一书中给出的定义:
"一个计算机程序如果能在某个任务 T 和某个性能衡量标准 P 下,随着经验 E 的增加,其在 T 任务上的表现(通过 P 衡量)得到改进,那么我们就说它从经验 E 中学习。"
上面的定义是我们刚才从直观角度讨论的机器学习(ML)更精确的版本。值得注意的是,今天我们所看到的大多数人工智能奇迹正是因为这种形式的人工智能得以实现。
我们现在对机器学习(ML)有了一个大致的了解。接下来,我们将进入下一部分,讨论机器学习中最强大的子领域——深度学习(DL)。我们不会深入讲解复杂的数学细节,而是像这一部分一样,直观地进行解析。
什么是深度学习(DL)?
接下来是最激动人心的部分,可能也是本世纪最火的技术术语。现实暂且不提,我们现在在某种程度上已经理解了学习,那么让我们来讨论术语深度学习中的第一个部分——深度。
深度学习(DL)是机器学习的一种类型,但它完全基于神经网络。我们将在下一章中讨论神经网络。任何机器学习系统的基本目标是学习所给数据的有用表示。那么,深度学习有什么不同呢?事实证明,深度学习系统将数据视为层次的表示。例如,一张图像可以视为不同特征层次的表示,特征层次包括边缘、轮廓、方向、纹理和梯度等。《Python 深度学习》一书中的以下图示很好地呈现了这个概念:
在前面的图中,一个深度学习系统被用来对手写数字的图像进行分类。系统以手写数字图像作为输入,并尝试学习其底层表示。在第一层,系统学习诸如笔画和线条等通用特征。随着层数的增加,它会学习到更加具体的特征。层数越多,系统就越深。我们来看一下弗朗索瓦·肖莱(François Chollet)在《Python 深度学习》一书中给出的定义:
"深度学习中的深度并不是指通过这种方法所获得的任何更深的理解;它实际上代表的是连续的层次化表示。多少层次的表示在数据模型中起到作用,这被称为模型的深度。[...] 在深度学习中,这些分层表示几乎总是通过称为神经网络的模型学习的,神经网络的结构是通过字面上的层次堆叠在一起的。"
这个定义恰如其分地捕捉到了深度学习的所有必要要素,并巧妙地引入了将数据视为分层表示的概念。因此,广义上的深度学习系统将数据分解成简单的分层表示,并且为了学习这些表示,它通常使用多层(这就是所谓的深度)。接下来,我们将从大局出发,了解 AI、ML 和 DL 之间的关系。
AI、ML 和 DL 之间的关系
为了确保我们对 AI、ML 和 DL 之间的区别有清晰的理解,下面的图表将帮助我们优雅地展示这三者之间的关系:
这个图表非常直观,且在许多深度学习领域的书籍中都有提到。让我们试着从这个图表中得出一个有趣的结论。
所有的深度学习系统都是机器学习系统,因此所有的深度学习系统也是人工智能系统。但反过来并不成立——并非所有的人工智能系统都是深度学习系统。
这个表述可能乍一看有些令人困惑,但如果我们掌握了基础,那么这句话就能很好地阐明 AI、ML 和 DL 之间的区别。接下来,我们将回顾一些必要的 ML 术语和概念,这些概念将在本书后续部分中用到。
重温 ML 的基本概念
我们已经了解了 ML 的含义。在本节中,我们将重点讨论几个术语,如监督学习和无监督学习,并将回顾标准 ML 工作流中涉及的步骤。但你可能会问:为什么是 ML?我们应该在本书中学习深度学习(DL)的应用。我们刚刚了解到,DL 只是一种 ML。因此,快速回顾一下基本的 ML 相关概念将会有所帮助。让我们从几种类型的 ML 及其相互区别开始。
ML 的类型
机器学习涵盖了众多的算法和话题。虽然每个构成机器学习模型的算法都只是对给定数据进行的数学计算,但提供的数据形式以及任务执行的方式可能有很大不同。有时,你可能希望你的机器学习模型根据过去房价的数据,结合房屋的详细信息(如房间数量和楼层数),预测未来的房价;而在其他时候,你可能希望你的机器学习模型学会如何与人对战玩电子游戏。对于第一个任务,你可以预期输入数据是表格格式的,但对于第二个例子,可能无法像第一个那样呈现。因此,机器学习算法根据它们接收的输入数据以及它们应该生成的输出类型,分为三大类及其衍生形式,具体如下:
-
监督学习
-
无监督学习
-
强化学习
-
半监督学习
下图展示了三种主要的机器学习类型,以及作为第四种类型的混合形式,并对每种类型进行了简要概述:
你可能听说过机器学习的第四种形式——半监督学习,它融合了监督学习和无监督学习的特点。
现在让我们根据它们的工作原理以及它们能够解决的问题类型,深入理解这些机器学习类型。
监督学习
在这种机器学习形式中,算法会接收到大量的训练样本,这些样本包含有关所有参数或特征的信息,这些参数或特征将用于确定输出特征。这个输出特征可以是一个连续的值域,也可以是一个离散的标签集合。基于这一点,监督学习算法可以分为两部分:
-
分类:在输出特征中生成离散标签的算法,如正常和不正常,或者一组新闻类别。
-
回归:当输出特征具有真实值时,例如,一个政党在选举中可能获得的票数,或一个材料在达到其熔点时的温度。
大多数机器学习爱好者在开始学习机器学习时,通常首先接触到监督学习,因为它直观且简单。它有一些最简单的算法,易于理解,即使没有深入的数学知识,也能理解,甚至这些算法源自数学学生在学校最后几年的学习内容。最著名的监督学习算法包括线性回归、逻辑回归、支持向量机和 k 近邻算法。
无监督学习
无监督学习适用于训练样本没有输出特征的情况。你可能会想,那么在这种情况下,我们究竟要学习或预测什么呢?答案是相似性。更具体地说,当我们有一个无监督学习的数据集时,我们通常是试图学习训练样本之间的相似性,然后为它们分配类别或标签。
想象一群人站在一个大草地上,他们都有年龄、性别、婚姻状况、薪资范围和教育水平等特征。现在,我们希望根据他们的相似性将他们分组。我们决定将他们分为三个组,看到他们根据性别自发地分为女性组、男性组和其他性别的组。然后,我们再要求他们在这些组内根据年龄段形成子组—儿童、青少年、成人和老年人。这样我们就得到了 12 个子组。我们还可以根据任何两个个体的相似性进一步细分子组。此外,前述的分组方式只是多种分组方式中的一种。现在,假设有 10 个新成员加入了这群人。由于我们已经定义好了组,因此可以很容易地将这些新成员分配到这些组中。因此,我们可以成功地为他们分配组标签。
上述例子展示了无监督学习的一种形式,它可以分为两种类型:
-
聚类:这是根据特征的相似性将训练样本分组。
-
关联:这是指发现特征或训练样本之间的抽象关联或规则。例如,通过分析商店的销售日志,发现顾客大多在晚上 7 点后购买啤酒。
K 均值聚类、DBSCAN 和 Apriori 算法是一些广为人知的用于无监督学习的算法。
强化学习
强化学习(RL)是一种机器学习方法,其中虚拟智能体尝试通过与环境的交互来学习,以便能够从某一特定的动作集合中获得最大的奖励。
让我们通过一个小例子来理解这一点——假设你构建了一个玩飞镖的机器人。现在,机器人只有在击中飞镖盘中心时才会获得最大奖励。它从一次随机的投掷开始,落在最外圈。它获得了一定的积分,假设是 x1。它现在知道,投掷到这个区域附近会得到预期值为 x1 的结果。所以,在下一次投掷时,它稍微改变了一下角度,幸运地落在了第二外圈,获得了 x2 积分。由于 x2 大于 x1,机器人取得了更好的结果,并且它将学会将飞镖投掷到该区域附近。如果飞镖落得比最外圈还远,机器人将不断向它的第一次投掷位置靠近,直到得到更好的结果。
在几次这样的试验中,机器人不断学习更好的投掷位置,并且从这些位置稍作偏离,直到找到下一个更好的投掷点。最终,它找到了靶心,并且每次都能获得最高的分数。
在前面的示例中,你的机器人是那个试图向飞镖盘投掷飞镖的智能体,而飞镖盘则是环境。投掷飞镖是智能体对环境执行的动作。智能体得到的积分充当奖励。在多次试验中,智能体通过执行不同的动作来尽可能最大化它获得的奖励。
一些著名的强化学习算法包括蒙特卡洛方法、Q 学习和 SARSA。
半监督学习
虽然我们已经讨论了三种主要的机器学习类型,但仍然存在另一种类型,那就是半监督学习。根据这个术语的名称,你可以猜到它与标注和未标注的训练样本混合有关。在大多数情况下,未标注的训练样本数量超过了标注样本的数量。
半监督学习在将一些标注样本加入到完全属于无监督学习的问题中时,已被成功地应用于产生更高效的结果。此外,由于只有少量样本被标注,因此避免了监督学习的复杂性。采用这种方法,我们可以得到比纯粹无监督学习系统更好的结果,同时也比纯监督学习系统消耗更少的计算资源。
必要的术语
我们已经了解了不同类型的机器学习系统。现在,我们将学习一些与机器学习密切相关的重要术语,这些术语将在本书后续章节中帮助我们理解。
训练集、测试集和验证集
任何机器学习系统都需要数据。没有数据,设计一个机器学习系统几乎是不可能的。现在我们并不关心数据的数量,但需要记住的是,我们需要数据来设计机器学习系统。一旦我们有了数据,就会用它来训练我们的机器学习系统,以便它们能在新数据上预测一些东西(这里的“东西”是一个广泛的术语,根据不同的问题有所不同)。因此,用于训练的数据显示为训练集,而用于测试模型的数据显示为测试集。另外,在实际将模型应用于测试数据之前,我们通常会在另一组数据上验证其性能,这组数据称为验证集。有时,我们无法直接获得这些精确划分的数据,我们只能得到原始、难以理解的数据,然后再根据需要进行处理和划分。
从技术上讲,这三组数据中的所有实例都应该彼此有所不同,同时数据中的分布应该是相同的。如今,许多研究者发现这些假设存在重要问题,并提出了一种叫做对抗训练的方法,这超出了本书的讨论范围。
偏差和方差
偏差和方差是任何机器学习模型中非常内在的特征。对它们有很好的理解,实际上有助于进一步评估模型。偏差和方差的权衡实际上是实践者用来评估机器学习系统性能的一个重要工具。
我鼓励你观看安德鲁·吴的这场讲座,了解更多关于这种权衡的内容,链接:www.youtube.com/watch?v=fDQkUN9yw44&t=293s
偏差是机器学习算法为学习给定数据中潜在的表示所做的一系列假设。当偏差较高时,意味着相应的算法对数据做出了更多的假设;而当偏差较低时,算法尽可能少地做出假设。一个机器学习模型在训练集上表现良好时,通常被认为具有低偏差。一些低偏差的机器学习算法包括 k 近邻和支持向量机,而像逻辑回归和朴素贝叶斯这样的算法通常是高偏差算法。
在机器学习中,方差涉及数据中存在的信息。因此,高方差指的是机器学习模型能够捕捉到数据中整体信息的质量。低方差则恰恰相反。像支持向量机这样的算法通常具有高方差,而像朴素贝叶斯这样的算法则具有低方差。
过拟合与欠拟合
当一个机器学习模型在训练数据上表现非常好,但在测试集或验证集上的表现较差时,这种现象被称为过拟合。造成这种现象的原因可能有多种,以下是最常见的一些:
-
模型相对于数据过于复杂。具有非常高层次的决策树或层数较多的神经网络是这类模型复杂性的典型例子。
-
数据有很多特征,但样本数量非常少。
在机器学习文献中,过拟合的问题也被视为高方差问题。正则化是防止过拟合的最常用方法。
我们已经讨论过偏差的概念。一个模型如果在训练数据上表现良好,则偏差较低,也就是说,模型在推断数据的表示时没有做出过多假设。如果模型在训练数据上表现糟糕,则称该模型有高偏差,并且该模型是欠拟合的。欠拟合可能有很多原因,以下是其中最常见的一些:
-
模型过于简单,无法学习到数据的潜在表示。
-
数据的特征在输入给机器学习模型之前没有进行有效的工程化。特征工程是这个过程中的重要部分。
基于这一讨论,我们可以得出一个非常有用的结论:一个过拟合的机器学习模型可能正遭遇高方差的问题,而一个欠拟合的模型可能正遭遇高偏差的问题。
如果没有以下图表,关于过拟合和欠拟合的讨论将是不完整的(该图由 Andrew Ng 在他的旗舰课程机器学习中展示):
上面的图表清晰地展示了通过数据点拟合曲线的欠拟合和过拟合情况。它还向我们展示了一个泛化良好的模型,即在训练集和测试集上都能表现良好的模型。蓝色的模型预测线远离样本,导致欠拟合,而在过拟合的情况下,模型捕捉了训练数据中的所有点,但并没有产生一个在训练数据以外能够表现良好的模型。
通常,将数据的表示学习视为近似最能描述数据的函数问题。一个函数可以像之前的图形一样轻松地绘制,因此,出现了曲线拟合的概念。模型能够很好地泛化的、介于欠拟合和过拟合之间的最佳位置被称为良好拟合。
训练误差和泛化误差
模型在训练阶段进行预测时所犯的错误统称为训练误差。模型在验证集或测试集上测试时所犯的错误被称为泛化误差。
如果我们要绘制这两种误差与偏差和方差(以及最终的过拟合和欠拟合)之间的关系图,它大致会是下面这样的(尽管这种关系不一定总是像图中所示的那样线性):
如果一个机器学习模型欠拟合(高偏差),那么它的训练误差必然较高。另一方面,如果模型过拟合(高方差),那么它的泛化误差较高。
我们将在接下来的部分中查看一个标准的机器学习工作流程。
一个标准的机器学习工作流程
任何项目都始于一个明确的问题,机器学习项目也不例外。在开始一个机器学习项目之前,明确理解你试图通过机器学习解决的问题是非常重要的。因此,问题的提出和与标准机器学习工作流程的映射是机器学习项目的良好起点。那么,机器学习工作流程指的是什么呢?这一部分将讨论这一主题。
设计机器学习(ML)系统并使用它们解决复杂问题需要一套除机器学习外的其他技能。需要知道的是,机器学习需要掌握多个方面的知识,如统计学、领域知识、软件工程、特征工程以及基础的高中数学,且各部分所需比例不同。要能够设计这样的系统,某些步骤对于几乎任何机器学习工作流程来说都是至关重要的,每个步骤都需要特定的技能。在这一部分,我们将简要了解这些步骤并讨论它们。
这个工作流程的灵感来源于CRISP-DM,它代表着跨行业数据挖掘标准流程,在涉及数据挖掘和分析的众多行业中广泛应用。
数据获取
正如本章前面所提到的,机器学习系统需要数据才能运行。数据并非总是可用,实际上,大部分时间数据本身并不是以可以直接用于训练机器学习模型的格式存在。但是,如果我们针对某个问题没有标准数据集,如何处理呢?这就是真实的情况!这种情况出现在大多数现实中的机器学习项目中。例如,假设我们想分析 2018 年新年决心相关的推文情感,并尝试估算出其中最有意义的内容。这实际上是一个没有标准数据集的问题。我们将不得不通过推特的 API 来抓取数据。另一个很好的例子是商业日志。商业日志是知识的宝藏。如果有效地挖掘和建模,它们可以帮助许多决策过程。但是,通常日志不会直接提供给机器学习工程师。因此,机器学习工程师需要花费大量时间去弄清楚日志的结构,并可能编写脚本来按照需求捕获日志。所有这些过程统称为数据获取或数据收集。
数据准备
在数据收集阶段之后,我们倾向于准备数据,将其输入到机器学习系统中,这被称为数据准备。值得一提的是,这是机器学习工作流/管道中最耗时的部分。数据准备包括一系列步骤,具体如下:
-
探索性数据分析
-
数据处理与整理
-
特征工程与提取
-
特征缩放和选择
这是机器学习项目中最耗时的部分之一。当我们从更广泛的角度审视整个过程时,我们发现数据识别和收集有时也非常重要,因为如前所述,正确的格式可能并不总是可用。
探索性数据分析(EDA)
数据收集完成后,数据准备阶段的第一步是探索性数据分析,通常简称为EDA。EDA 技术使我们能够更详细地了解数据,从而更好地理解数据。这是整个机器学习管道中极其重要的一步,因为如果我们对数据本身缺乏充分的了解,盲目地将机器学习模型应用于数据,很可能不会产生好的结果。EDA 为我们提供了前进的方向,并帮助我们决定管道中的后续步骤。EDA 涉及许多内容,如计算数据的有用统计量,并确定数据是否存在离群值。它还包括有效的数据可视化,帮助我们图形化地解读数据,从而以有意义的方式传达数据的关键信息。
简而言之,EDA 的核心就是更好地了解数据。
数据处理与整理
我们已经对数据进行了某些统计分析。现在怎么办?大多数情况下,从多个数据源收集的数据以原始形式呈现,无法直接输入到机器学习模型中,因此需要进一步的数据处理。
但你可能会问,为什么不以一种方式收集数据,使其在检索时就完成所有必要的处理?这通常不是一个好的做法,因为它破坏了工作流的模块化。
正因为如此,为了使数据在工作流后续步骤中可以使用,我们需要对其进行清理、转换和持久化。这包括多项工作,如数据归一化、数据标准化、缺失值填补、从一个值到另一个值的编码转换以及离群值处理。所有这些统称为数据整理。
特征工程与提取/选择
假设有一家分析公司的一名员工收到公司的账单数据,并被经理要求使用这些数据建立一个机器学习系统,以便优化公司的整体财务预算。现在,这些数据的格式无法直接提供给机器学习模型,因为机器学习模型期望数据以数值向量的形式呈现。
虽然数据可能已经处理得不错,但员工仍然需要做某些事情,以将这些数据转换为有利的形式。尽管数据已经经过清理,但他们仍然需要决定哪些特征要包含在最终数据集中。实际上,任何可度量的内容都可以是一个特征。此时,良好的领域知识非常重要。这些知识能够帮助员工选择具有高预测能力的特征。听起来可能有点简单,但这实际上需要大量的技能,并且绝对是一个具有挑战性的任务。这是特征工程的经典例子。
有时,我们采用一些技术来帮助我们从给定的数据集中自动提取最有意义的特征。特别是在数据维度非常高且特征难以解释时,这一点特别有用。这被称为特征选择。特征选择不仅有助于开发一个具有最相关特征的机器学习模型,还能提高模型的预测性能并减少计算时间。
除了特征选择,我们可能还希望通过降维来更好地可视化数据。此外,降维还用于从完整的数据特征集中捕获一组具有代表性的特征。主成分分析(PCA)就是一种非常流行的降维技术。
需要牢记的是,特征选择和降维并不是同一回事。
建模
我们终于来到了看似最激动人心的步骤——机器学习建模部分。但值得注意的是,一个好的机器学习项目不仅仅是关于这一部分。之前提到的所有部分同样对项目标准的提升起到了关键作用。实际上,数据是如何收集的对项目来说非常重要,为此,我们有强大的数据工程师提供帮助。现在,先把这部分放到一边。
到目前为止,我们已经将数据处理得相当好。在建模过程中,我们将训练数据输入到机器学习模型中进行训练,监控它们的训练进度,并调整不同的超参数以优化其性能,然后在测试集上评估模型。模型比较也是这一阶段的一部分。事实上,这确实是一个迭代的过程,并在一定程度上涉及试错。
这里的主要目标是提出一个最佳地表示数据的机器学习模型,即它能够很好地泛化。计算时间是我们在这里必须考虑的另一个因素,因为我们需要一个在合理时间框架内表现良好的模型,从而优化某个业务结果。
以下是构成建模核心部分的内容:
-
模型训练
-
模型评估
-
模型调优
模型训练
这是建模的基础部分,我们将数据输入不同的机器学习模型并训练模型,以使其能够全面地学习数据的表示。我们可以通过训练误差来看模型在训练过程中如何进展。我们通常还会引入验证误差(即我们同时验证模型训练),这是一个标准做法。现在的现代库大多数都支持这样做,我们将在本书接下来的章节中看到。接下来我们将讨论一些最常用的误差指标。
模型评估
我们已经训练了一个机器学习模型,但它在从未见过的数据上表现如何呢?我们用模型评估来回答这个问题。
不同的机器学习算法需要不同的评估指标。
对于监督学习方法,我们通常使用以下指标:
-
混淆矩阵,它由四个值组成:真正例、假正例、真负例和假负例
-
准确率、精确率、召回率和 F1 分数(这些都是混淆矩阵的衍生指标)
-
接收者操作特征(ROC)曲线和曲线下面积(AUC)指标
-
R 平方(决定系数)、均方根误差(RMSE)、F 统计量、赤池信息量准则(AIC)以及专门针对回归模型的 p 值
在本书中,我们将结合这些指标来评估我们的模型。虽然这些是最常见的评估指标,无论是针对机器学习(ML)还是深度学习(DL),但针对不同领域还有更具体的评估指标。我们将在后续章节中讲到这些内容。
在这里值得一提的是,在分类问题中,特别是当数据不平衡时,我们常常会陷入准确性悖论的陷阱。在这些情况下,分类准确率只能反映一部分情况,即它给出了正确预测的百分比,而不是模型对负类实例(即最初的问题——预测不常见类别)的预测能力。这种系统在数据不平衡的情况下会失败,因为准确性并不能反映模型在预测负类实例时的表现。
以下是评估无监督方法(如聚类)时最常用的指标:
-
轮廓系数
-
平方误差之和
-
同质性、完整性和 V 度量
-
卡林斯基-哈拉巴兹指数
评估指标/误差指标对于训练集、测试集或验证集是相同的。我们不能仅仅通过查看模型在训练集上的表现就得出结论。
模型调优
到了这一阶段,我们应该已经有了一个基准模型,可以在此基础上进一步调整模型,使其表现更好。模型调整对应于超参数调整/优化。
机器学习模型有不同的超参数,这些超参数无法通过模型训练来学习。它们的值由从业者设置。你可以将超参数的值比作音频均衡器的旋钮,我们手动调整旋钮以获得完美的听觉体验。在后续章节中,我们将看到超参数调优如何极大地提升模型的性能。
有几种技术可以用来调优超参数,最常用的有以下几种:
-
网格搜索
-
随机搜索
-
贝叶斯优化
-
基于梯度的优化
-
进化优化
模型比较与选择
在完成模型调优部分后,我们肯定会希望对当前模型之外的其他模型重复整个建模过程,希望能得到更好的结果。作为机器学习从业者,我们的职责是确保最终得出的模型比其他模型更优秀(显然是在多个方面)。自然地,比较不同的机器学习模型是一项耗时的任务,而当我们需要赶在短时间内完成任务时,可能无法总是进行此操作。在这种情况下,我们会考虑以下几个模型方面:
-
可解释性,它回答了一个特定问题(模型的可解释性如何?它有多容易被解释和传达?)
-
内存中建模与外存建模
-
数据集中的特征和实例数量
-
类别特征与数值特征
-
数据的非线性
-
训练速度
-
预测速度
这些指标是最常见的,但它们在很大程度上取决于所面临的问题。当这些指标不适用时,一个好的经验法则是查看模型在验证集上的表现。
部署与监控
在构建好机器学习模型后,它将与应用程序的其他组件合并,并投入生产。这个阶段称为模型部署。开发的机器学习模型的真实表现会在部署到实际系统后进行评估。此阶段还涉及对模型的全面监控,找出模型表现不佳的领域,以及可以进一步改进的方面。监控至关重要,因为它为提升模型的表现提供了手段,从而提升整个应用程序的表现。
这就是进行机器学习项目时最重要的术语/概念的简要介绍。
为了更深入地学习机器学习基础,建议你通过以下资源进行学习:Google 的机器学习速成课程(developers.google.com/machine-learning/crash-course/
)和 Sebastian Raschka 的Python 机器学习(india.packtpub.com/in/big-data-and-business-intelligence/python-machine-learning
)。
为了方便参考,你可以查看书中给出的以下图示,来自《Python 实践转移学习》(Dipanjan 等人),该图以图像的形式展示了前面提到的所有步骤:
实际上,机器学习(ML)在各个领域带来了大量的改进,几乎没有一个领域没有受到它的影响。本书的重点是构建智能网页应用程序。因此,我们将在下一部分开始讨论网页的整体情况以及自人工智能出现以来它是如何变化的,采用前后对比的方式。最终,我们将学习一些大公司及其如何利用人工智能构建世界级的网页应用,这些应用不仅智能,而且解决一些实际问题。
人工智能前后的网页
如果你自 2014 年起就是万维网的常规用户,你会同意网站的变化速度是显而易见的。从解决越来越难以辨认的ReCaptcha挑战到在后台自动标记为人类,网页开发一直是展示过去二十年里人工智能成果的先行者之一。
被誉为互联网发明者的蒂姆·伯纳斯-李爵士提出了他的语义网观点:
"我对 Web 的梦想是[计算机]能够分析 Web 上的所有数据——内容、链接以及人类与计算机之间的交易。一个实现这一目标的“语义网”尚未出现,但一旦它出现,日常的贸易、官僚机制以及我们日常生活的各个方面将由机器与机器之间的对话来处理。人们长期以来推崇的“智能代理”将最终得以实现。"
从呈现大量信息并且其中的链接将你永久带到相关资源的静态页面,到如今网页已经成为一个动态生成信息、不断变化的门户。如果你刷新网页,你可能再也看不到相同的页面视图。
让我们了解一下由于人工智能的兴起,网页开发发生的一些最重要的变化。
聊天机器人
如果你曾经好奇,为什么有些网页能通过聊天窗口提供 24 小时全天候帮助,答案几乎总是一个聊天机器人在另一个端口回答你的问题。当 1966 年约瑟夫·魏岑鲍姆的 ELIZA 聊天机器人通过图灵测试而引起全球轰动时,我们几乎无法预见聊天机器人将会在万维网上产生如此巨大的影响(不过一个原因可能是 ARPANET 直到 1969 年才创建)。
今天,聊天机器人无处不在。许多《财富》500 强公司都在这个领域进行研究,并推出了针对其产品和服务的聊天机器人实现。在 Oracle 进行的一项最新调查中,涵盖了来自多个公司和初创公司的 800 名高管的反馈,调查发现近 80%的受访者表示,他们已经使用或计划在 2020 年之前将聊天机器人应用于面向客户的产品。
在人工智能开始驱动聊天机器人之前,像 ELIZA(及其后继者 ALICE)这样的聊天机器人,通常只是根据几个输入模式映射到一组固定的响应。当用户输入句子中出现母亲或父亲这类词时,几乎肯定会得到询问用户家庭或健康状况的回复。如果用户写下类似“我不想谈论 XYZ 的家庭”的内容,这显然并不是他们期望的回答。
然后,还有那种经典的“抱歉,我没听懂”的回答,这种基于规则的聊天机器人有时显得非常愚笨。神经网络算法的出现使得聊天机器人能够根据用户情感和输入的上下文理解并定制回应。此外,一些聊天机器人会抓取在线数据,以便在遇到新问题时实时构建与新问题中提到的话题相关的回答。除此之外,聊天机器人还被用来为商业门户提供替代界面。现在,通过 WhatsApp 提供的聊天平台,你可以预定酒店或航班。
Facebook Messenger 的机器人平台在对公众开放的前 17 个月内创造了超过 100,000 个机器人。如今,社交网络巨头 Facebook 上的数百个页面都为向其页面发送消息的用户提供自动回复。一些机器人在 Twitter 上运行,能够生成内容,* closely* 模仿人类用户,并且可以回复用户在其帖子上的消息或评论。
你可以在eliza.botlibre.com与 ELIZA 的在线版本聊天。
网络分析
在互联网的早期,许多网站都嵌入了类似里程计的计数器。这些计数器简单地统计网站或某个特定页面收到的访问次数。后来,计数器的形式也越来越多样化——包括普通计数器、按天/周/月计数的计数器,甚至基于地理位置的计数器。
网络分析是指收集数据,这些数据本质上是用户的交互日志以及他们如何与基于网络的应用程序互动,处理这些数据以产生绩效指标,然后最终识别出可以采取的措施,帮助公司改善其网络应用程序。
自互联网发明以来,今天的网络应用程序每时每刻都会生成大量的日志。即使你将鼠标指针保持在网页上不动,可能也会被报告到 Google Analytics 仪表盘,从而网站管理员可以查看用户访问了哪些页面,以及他们在页面上停留了多长时间。此外,用户在各页面之间的流动路径也是一个非常有趣的指标。
虽然最早的网络分析工具仅仅测量页面访问次数,能够创建一个页面被访问多少次、多少次是独立用户访问的图示,但它们几乎无法提供用户访问模式的任何信息,除非这些信息被专门硬编码进去,这些数据通常以非常笼统的方式呈现,并且从未与网站特定的内容相关联。相同的分析形式被提供给电商公司,也同样被提供给个人网站。
随着人工智能在网络分析领域带来的革命,今天的工具可以利用人工智能的力量,预测网站未来的表现,甚至建议删除或添加网页上的特定内容,以提高用户与网页的互动。
垃圾邮件过滤
当全球一半的电子邮件被标记为垃圾邮件时,这就成了一个问题。虽然我们最初可能会将那些推广业务和产品的欺诈性或不必要的电子邮件视为垃圾邮件,但这仅是其定义的一部分。重要的是要意识到,即使是优质的内容,当它在同一文档中重复多次发布时,也会成为垃圾邮件。此外,自从“垃圾邮件”这一术语首次出现在 Usenet 群组中以来,互联网已经发生了巨大变化。最初,垃圾邮件是为了打扰他人或强制推送信息给特定目标用户而进行的活动,但今天的垃圾邮件已经变得更加复杂,潜在地也更加危险——从能够追踪你的浏览器活动到身份盗窃,今天的互联网充斥着大量危害用户安全和隐私的恶意垃圾邮件。
今天,我们面临着各种类型的垃圾邮件——即时通讯垃圾邮件、网站垃圾邮件、广告垃圾邮件、短信垃圾邮件、社交媒体垃圾邮件,以及许多其他形式的垃圾邮件。
除了少数几种,大多数类型的垃圾邮件都出现在互联网上。因此,能够过滤垃圾邮件并采取保护措施至关重要。虽然最早的反垃圾邮件工作可以追溯到 1990 年代,那时通过识别发送垃圾邮件的 IP 地址来进行处理,但很快就意识到这种方法效率极低,因为黑名单变得越来越庞大,且其分发和维护非常繁琐。
在 2000 年代初,当保罗·格雷厄姆发布了一篇名为《垃圾邮件计划》的论文时,首次使用了机器学习模型——贝叶斯过滤——来应对垃圾邮件。很快,几款反垃圾邮件工具便从这篇论文中衍生出来,并证明它们非常高效。
贝叶斯过滤方法对抗垃圾邮件的影响如此之大,以至于在 2004 年世界经济论坛上,微软创始人比尔·盖茨曾表示:
“两年后,垃圾邮件问题将会得到解决。”
然而,正如我们今天所知,比尔·盖茨在这个预测上完全错了。垃圾邮件不断演变,垃圾邮件发送者研究贝叶斯过滤方法,找出了规避检测阶段标记为垃圾邮件的方式。如今,神经网络被大规模部署,持续扫描新的电子邮件并决定是否为垃圾邮件,这些判断是人类仅仅通过研究垃圾邮件日志无法做出的。
搜索
人工智能的崛起对网络搜索领域产生了深远影响。从最初必须精确知道你希望访问的网页标题的准确措辞,到现在搜索引擎能够识别你周围环境中的歌曲,这一领域因人工智能而彻底改变。
当 1991 年蒂姆·伯纳斯-李建立了万维网虚拟图书馆时,它大致是这样的:
这是一个手动列出的网页集合,可以通过出现在右上角的搜索框进行过滤。显然,与其预测用户的搜索意图,用户自己必须决定其搜索词所属的类别。
当前网页搜索引擎的面貌是在 1993 年 12 月由 Johnathan Fletcher 提出的,当时他创建了 JumpStation,这是第一个使用现代爬虫、索引和搜索概念的搜索引擎。JumpStation 的界面设计正是我们今天看到的 Google 和 Bing 等主流搜索引擎的样子,这使得 Johnathan 成为了“搜索引擎之父”。
两年后,即 1995 年 12 月,当 AltaVista 推出时,它带来了搜索技术的根本变化——无限带宽、搜索提示,甚至允许自然语言查询——这一功能在 1997 年被 Ask Jeeves 更强力地引入。
Google 于 1998 年诞生,并带来了 PageRank 技术。然而,当时市场上有多个竞争者,Google 并没有立即主导搜索引擎市场。五年后,当 Google 申请使用神经网络根据用户的历史搜索记录和访问过的网站定制搜索结果的专利时,局势迅速发生变化,Google 很快成为搜索领域最强大的提供者。
今天,Google 搜索依靠着一个庞大的代码库,部署了多个协同工作的深度神经网络。自然语言处理,主要依靠神经网络,已使 Google 能够判断网页内容的相关性,而得益于卷积神经网络(CNNs),机器视觉已经能够在 Google 图片搜索中生成准确的结果。令人不意外的是,John Ginnandrea 曾领导 Google 搜索并推出了知识图谱(Google 有时会给出答案的某些问题,如查询结果);他是 AI 领域最受追捧的专家之一,现在已经被 Apple 招募,以改进 Siri,Siri 也是一个神经网络产品。
最大的网络 AI 公司及其在 AI 领域的应用
人工智能的快速增长吸引了众多竞争者争相利用这一机会。在过去的二十年里,多个个人、初创公司甚至大型企业都在寻求从人工智能应用中获取利益。市场上已经出现了一些产品,其中人工智能是它们业务的核心。
"战争是 90%的信息。"
—— 拿破仑·波拿巴,公元 18 世纪
在第二次世界大战中,盟军部署了轰炸机。这些轰炸机是盟军策略的关键。但由于大量轰炸机在敌方领土被击落,这些轰炸机未能完成任务。显然,轰炸机需要更多的装甲。然而,由于装甲重量的限制,无法完全覆盖整个飞机。因此,决定将飞机最关键的部位加装额外的装甲。犹太数学家 Abraham Wald 被要求提出一个方法,来确定哪些部位需要装甲保护。他研究了从战斗中归来的飞机,并记录下哪些部位承受了最多的弹孔。
研究发现,机翼、机头和尾部是承受最多弹孔的部分,因此得出结论,这些部分需要更多的装甲,而驾驶舱和发动机则显示出最少的弹孔:
但令人惊讶的是,Wald 持不同的观点,他建议应加强驾驶舱和发动机的装甲,因为正是那些未能返回的轰炸机才是问题所在。尾部、机翼和机头的弹孔不会对飞机造成致命伤害,因此这些飞机能够成功返回。
就是这样,通过与数据的合作,识别出正确的模式,一位数学家改变了整个第二次世界大战的进程。数据被称为新的石油。更有趣的是,当你拥有石油时,你会将其燃烧以产生电力和能源,驱动车辆。而拥有数据时,你利用它来改善业务和做出决策,进而在未来产生更多的数据。那些意识到这一点并最大化利用现有数据的公司,近年来实现了巨大的增长。让我们看看这些公司如何利用所有数据,通过 AI 创造新机会。
谷歌
提到 AI 这个词几乎每个人都会想到的一个名字,谷歌不断地推动并革命化了 AI 的边界。
“我们现在正在见证计算领域的一个新转变:从移动优先到 AI 优先的世界。”——桑达尔·皮查伊,谷歌 CEO
谷歌已经在多个产品中应用 AI,接下来我们来看看其中的一些。
谷歌搜索
2018 年 12 月 14 日,搜索谁是谷歌 CEO
,得到的结果页面大致如下图所示:
上述功能,通过生成常见问题的答案,被称为谷歌知识图谱,我们在之前的章节中提到过。除了这一功能,得益于自然语言处理和信息提取等 AI 技术,谷歌搜索的功能也变得更加强大。
通过 AI 技术,能够准确地在视频中提取与用户查询相关的时间点:
接下来,我们将看一下谷歌翻译。
谷歌翻译
支持超过 100 种语言,谷歌翻译可能是互联网上最好的公共翻译工具。从能够检测输入的语言,到将其转换成用户设定的目标语言,背后有一张深度神经网络在运行,以提供最佳的翻译效果。这个算法,谷歌自 2016 年 11 月起切换使用,被命名为谷歌神经机器翻译算法。它以 API 形式提供给网页开发者,帮助他们将网站内容实时翻译,以便能够服务不同地区的用户。此外,这项服务还与谷歌浏览器 Google Chrome 集成,能够在用户访问网页时,实时翻译网页内容。
谷歌助手
Google 的最新项目之一——Google Assistant,是 Apple 的 Siri 和 Microsoft 的 Cortana 的竞争者,也是 Google Now 的继任者。它是一个基于 AI 的虚拟助手,可在移动设备和智能家居设备(品牌为Google Home)上使用。目前,它可以在用户的 Google Drive 数据上进行搜索,根据用户的偏好生成结果,提供用户给出的备忘提醒,拨打电话、发送短信,并根据用户的指示,通过触摸屏上的正常点击输入或语音输入执行更多任务:
接下来,我们将看看其他产品。
其他产品
AI 是 Google 广告的主要技术之一。点击诱饵或虚假点击问题通过神经网络得到了有效解决。此外,通过使用 AI,可以高效地确定哪种类型的广告在每个网页上的表现最佳。这些技术进步使得 Google 的广告服务迅速抢占了互联网广告市场,超越了之前的广告平台。
Google 的一些项目,如 Google Lens、自动驾驶汽车等,主要都是基于 AI 的项目。
作为互联网上最大的社交平台,Facebook 每天生成大量数据。用户发布的内容、用户提交的报告、Facebook 提供的各种 API 的日志等等,所有这些加起来每天会产生近 4 个拍字节的数据。不用多说,这个科技巨头已经利用这一数据宝藏,想出了方法来使其平台更安全,并提升用户参与度。
虚假账户
Facebook 面临的一个主要问题是存在大量的虚假账户。为了解决这个问题,Facebook 部署了基于 AI 的解决方案,自动标记并挑战这些账户,确认其身份。仅在 2018 年第一季度,Facebook 就禁用了近 5.83 亿个虚假或克隆账户。
假新闻与令人不安的内容
另一个 Facebook 及其收购的消息服务 WhatsApp 面临的问题是虚假新闻或误导性新闻。此外,平台上出现的视觉上和/或情感上令人不安的内容,进一步降低了用户体验。最后,还有几乎所有在线平台都必须应对的问题:垃圾邮件。Facebook 的 AI 算法多年来在识别和删除垃圾邮件方面变得非常擅长。通过应用计算机视觉解决方案,利用 CNN(卷积神经网络),Facebook 开发了一项功能,可以遮挡/模糊令人不安的图像和视频,并在允许用户查看之前要求用户同意。
识别并删除假新闻的工作目前正在进行中,几乎完全通过 AI 的应用来实现。
其他用途
Facebook 提供了自己的 Messenger 机器人平台,Facebook 页面和开发者广泛使用该平台,将丰富的互动功能加入公司提供的即时消息服务中。
亚马逊
作为全球领先的电子商务平台,亚马逊几乎将 AI 融入了其所有的产品和服务中。虽然它是比 Google、Facebook、Microsoft 和 IBM 等公司晚加入 AI 阵营的,但亚马逊迅速崛起,并吸引了人们对其 AI 应用的关注。让我们看看亚马逊推出的一些主要应用。
Alexa
为所有由公司生产的 Alexa 和 Echo 设备提供支持的 AI,Alexa 是与 Google Home 竞争的虚拟助手 AI 的名字,后者由 Google Assistant(前身为 Google Now)提供支持。不讨论哪个更好,Alexa 是一个相当先进的 AI,能够回答许多用户觉得有趣和机智的问题。随着亚马逊将 Alexa Skills Studio 向开发者公开,Alexa 产品的使用已显著增长,开发者为 Alexa 能执行的操作做出了巨大贡献。
亚马逊机器人技术
当用户在网站上购买产品时,位于华盛顿州肯特市、占地 855,000 平方英尺的巨大配送中心内的一台机器人便会启动,搬起一大箱产品,朝着站点移动,将在平台上售出的产品送到工人手中,工人会从箱子中取出并进一步处理。亚马逊最近在密尔沃基配送中心安装了相同的技术,并计划在不久后将其扩展到其他 10 个大型中心。
DeepLens
一台人工智能驱动的视频摄像机曾是 2000 年代初的极客梦想。随着亚马逊推出的 DeepLens,这一梦想成为现实,开启的可能性是无穷的。试想一下,当你是派对的主持人时,手机会通知你每个到来的宾客。令人惊讶的是,这已经实现,甚至已经进行过在公共场所安装 CCTV 摄像头来识别罪犯并自动触发警报的实验。
总结
在本章中,我们简要介绍了许多对执行 ML 项目至关重要的重要概念和术语。这些内容将在本书中为您提供帮助。
我们从人工智能(AI)是什么以及它的三种主要类型开始。我们观察了推动周围 AI 爆炸性增长的因素。接着,我们简要了解了机器学习(ML)的几个组件以及它们如何在 ML 项目中发挥作用。我们看到了什么是深度学习(DL),以及 AI、ML 和 DL 是如何相互关联的。
在本章的最后,我们看到了几个例子,其中 AI 与 Web 技术相结合,创建智能应用,承诺解决复杂问题。几乎所有支持 AI 的应用背后都有深度学习(DL)的身影。
在接下来的章节中,我们将利用深度学习(DL)来构建智能网页应用。
第三章:使用深度学习进行网页开发
本节介绍了与深度学习相关的基本概念和术语,并讲解了如何使用深度学习以及不同的 Python 深度学习库来构建一个简单的网页应用程序。
本节包括以下章节:
-
第二章,使用 Python 入门深度学习
-
第三章,创建你的第一个深度学习网页应用
-
第四章,TensorFlow.js 入门
第四章:使用 Python 开始深度学习
在第一章中,我们深入探讨了深度学习以及它与机器学习和人工智能之间的关系。在本章中,我们将更深入地探讨这一主题。我们将从了解深度学习的核心——神经网络及其基本组成部分开始,包括神经元、激活单元、反向传播等内容。
请注意,本章不会过多涉及数学内容,但同时,我们也不会忽略那些对神经网络世界至关重要的最基本公式。对于更偏重数学的学习,建议读者阅读 Goodfellow 等人所著的《深度学习》一书(deeplearningbook.org)。
以下是我们将在本章中涵盖内容的概述:
-
神经网络及其相关概念的快速浏览
-
深度学习与浅层学习的区别
-
不同类型的神经网络
-
设置基于深度学习的云环境
-
探索 Jupyter Notebooks
解开神经网络的谜团
本节的开始,我们将寻找问题的答案:“为什么神经网络被称为'神经'网络?”这个术语背后有什么意义?
我们的直觉告诉我们,它与我们的脑部有关系,这是正确的,但仅仅部分正确。在了解为什么这种理解只部分正确之前,我们需要对大脑的结构有所了解。为此,让我们来看看我们自己大脑的解剖结构。
人类大脑由大约 100 亿个神经元组成,每个神经元与大约 10,000 个其他神经元相连接,形成一种类似网络的结构。神经元的输入被称为树突,输出被称为轴突。神经元的主体部分称为胞体。因此,从高层次看,某个特定的胞体与另一个胞体相连接。 "神经"一词源自"神经元"一词,实际上,"神经"是"神经元"的形容词形式。在我们的脑中,神经元是形成我们刚刚讨论的这一密集网络的最基本单元。我们正在慢慢理解人工神经网络与大脑之间的相似性,为了进一步理解这种相似性,我们将简要了解神经元的功能。
网络不过是一个类似图形的结构,包含一组节点和彼此连接的边。在我们的脑部,或任何大脑中,神经元被称为节点,树突被称为顶点。
神经元通过其树突接收来自其他神经元的输入。这些输入是电化学性质的。并非所有输入的力量相等。如果输入的力量足够强大,则连接的神经元会被激活,并继续将输入传递给其他神经元。它们的强度由一个预设的阈值决定,这个阈值使得激活过程具有选择性,从而避免在同一时间激活网络中所有的神经元。
总结来说,神经元接收来自其他神经元的输入总和,这个总和与一个阈值进行比较,然后神经元根据阈值激活。人工神经网络(ANN),或者简称神经网络(NN),就是基于这一重要事实,因此才有了这种相似性。
那么,是什么让一个网络成为神经网络呢?要构成一个 NN 需要什么?
以下引用来自 Adrian Rosebrock 的《Deep Learning For Computer Vision With Python》一书,以非常值得称赞的方式回答了这个问题:
每个节点执行简单的计算。然后,每个连接携带一个信号(即计算的输出)从一个节点传递到另一个节点,连接上标有一个权重,表示信号被放大或减弱的程度。有些连接的权重大且为正,放大了信号,表示在分类时这个信号非常重要。其他连接则具有负权重,削弱了信号的强度,说明该节点的输出在最终分类中的重要性较低。如果一个系统由带有可通过学习算法修改的连接权重的图形结构组成,我们就称这样的系统为人工神经网络。
我们已经了解了神经网络与大脑的相似性。接下来,我们将利用这些信息进一步了解 ANN 中的基本单元。让我们从了解 ANN 中一个简单神经元的作用开始。
人工神经元
我们称在 ANN 中使用的神经元为人工神经元。广义来说,人工神经元可以分为两种类型:
-
线性神经元
-
非线性神经元
线性神经元的结构
神经元是神经网络中最基本的单元。让我们看看“神经网络”这个词的第二个词。网络仅仅是一些顶点(也叫节点)的集合,这些顶点的边相互连接。在神经网络中,神经元充当节点。让我们考虑以下神经网络架构,并尝试逐步分析:
我们在前面的图示中看到的是一个具有两个隐藏层的神经网络(在神经网络中,层是神经元的集合),并且只有一个输出。事实上,这种网络被称为两层神经网络。神经网络由以下几个部分组成:
-
单一输入
-
两个隐藏层,其中第一个隐藏层有三个神经元,第二个隐藏层包含两个神经元
-
单一输出
所谓隐藏层并没有更深的心理学意义,它们被称为隐藏层仅仅是因为这些层中的神经元既不是输入层的一部分,也不是输出层的一部分。这里有一个非常明显的地方,那就是在第一个隐藏层之前有一层。为什么我们不把这一层算在内呢?在神经网络的世界里,初始层和输出层并不算在层堆叠中。简单来说,如果有n个隐藏层,那么它就是一个n层的神经网络。
初始层(也叫输入层)用于接收神经网络的主要输入。在接收到主要输入后,输入层中的神经元将其传递给后续隐藏层中的神经元。在这一传播过程发生之前,神经元会给输入加上权重,并为输入添加一个偏置项。这些输入可以来自不同领域——例如,输入可以是图像的原始像素、音频信号的频率、一组词语等等。通常,这些输入会作为特征向量传递给神经网络。在这种情况下,输入数据只有一个特征。
那么,接下来的两层神经元在做什么呢?这是一个重要的问题。我们可以将权重和偏置添加到输入的过程看作是学习的第一层/级(也叫决策层)。初始隐藏层中的神经元重复这个过程,但在将计算结果发送到下一个隐藏层的神经元之前,它们会将这个值与阈值进行比较。如果阈值条件被满足,那么输出才会传播到下一层。这部分整个神经网络学习过程与我们之前讨论的生物学过程有着显著的相似之处。这也支持了以分层的方式学习复杂事物的理念。
这里有一个问题是:“如果不使用隐藏层,会发生什么?”事实证明,在神经网络中添加更多的复杂度(通过增加更多层)可以比仅有输入层和输出层的网络更简洁地学习输入数据的潜在表示。那么,我们需要多少层呢?我们稍后会讲到这个问题。
让我们在这里引入一些数学公式,来正式化我们刚才学到的内容。
我们将输入特征表示为x,权重表示为w,偏置项表示为b。我们目前要剖析的神经网络模型基于以下规则:
该规则表示,在计算加权输入和偏置的和之后,如果结果大于 0,则神经元输出 1;如果结果小于或等于 0,则神经元输出 0,换句话说,神经元不会激活。对于多个输入特征,规则仍然完全相同,其多元版本如下:
这里,i表示我们有i个输入特征。上述规则可以分解如下:
-
我们逐个处理特征,然后将它们与权重相乘
-
在对所有输入特征完成该过程后,我们将所有加权输入求和,并最终加上偏置项。
上述过程会继续进行,直到我们网络中的所有层都完成。在这种情况下,我们有两层隐藏层,因此一层的输出会被输入到下一层。
我们刚刚学习的元素是由 Frank Rosenblatt 在 1960 年代提出的。将输入的加权和基于某个阈值分配 0 或 1 的想法也被称为阶跃函数。文献中有许多类似的规则,这些被称为更新规则。
我们研究的神经元是线性神经元,它们能够学习线性函数。但它们不适合学习本质上是非线性的表示。实际上,几乎所有神经网络接收的输入都是非线性的。在下一节中,我们将介绍另一种类型的神经元,它能够捕捉数据中可能存在的非线性。
你们中的一些人可能会想,这个神经网络模型是不是叫做MLP(多层感知机)。嗯,确实是。实际上,Rosenblatt 早在 1960 年代就提出了这个模型。那么,什么是神经网络呢?我们很快就会学到答案。
非线性神经元的构造
非线性神经元意味着它能够响应数据中可能存在的非线性。这里的非线性基本上意味着,对于给定的输入,输出不会以线性方式变化。请看下面的图示:
上述两个图都描述了神经网络接收的输入与网络产生的输出之间的关系。从第一个图中可以看出,输入数据是线性可分的,而第二个图则告诉我们,输入数据不能线性分割。在这种情况下,线性神经元将会失败,因此需要非线性神经元。
在神经网络的训练过程中,可能会出现小幅度改变偏置和权重值会对神经网络的输出产生剧烈影响的情况。理想情况下,这种情况不应发生。对偏置或权重值的微小改变应该只导致输出的微小变化。当使用 step 函数时,权重和偏置的变化可能会对输出产生较大的影响,因此需要使用其他的函数来替代 step 函数。
神经元的操作背后有一个函数。在线性神经元的情况下,我们看到它的操作是基于 step 函数的。我们有一些函数能够捕捉非线性问题。sigmoid 函数就是其中之一,使用这种函数的神经元通常被称为 sigmoid 神经元。与 step 函数不同,sigmoid 神经元的输出是根据以下规则生成的:
所以,我们最终的更新规则变为以下形式:
那么,为什么在捕捉非线性时,sigmoid 函数比 step 函数更好呢?让我们通过图形来比较它们的表现,理解这一点:
前面两张图清晰地展示了这两个函数的内在特性。显而易见,sigmoid 函数对非线性问题的敏感度高于 step 函数。
除了 sigmoid 函数,以下是一些广泛知名且常用于赋予神经元非线性特征的函数:
-
Tanh
-
ReLU
-
Leaky ReLU
在文献中,这些函数以及我们刚刚学习的两个函数,被称为激活函数。目前,ReLU 及其变体无疑是最成功的激活函数。
我们仍然有一些与人工神经网络相关的基本概念没有讲解。让我们总结一下到目前为止学到的内容:
-
神经元及其两种主要类型
-
层
-
激活函数
我们现在可以在多层感知机(MLP)和神经网络之间做出区分。Michael Nielson 在他的在线书籍《神经网络与深度学习》中很好地描述了这一点:
有些时候,由于历史原因,这类多层网络有时会被称为多层感知机(MLP),尽管它们是由 sigmoid 神经元组成,而非感知机。
在本书中,我们将贯穿始终地使用神经网络和深度神经网络的术语。接下来,我们将继续学习神经网络的输入层和输出层。
关于神经网络的输入层和输出层的说明
了解什么可以作为神经网络的输入非常重要。我们是否将原始图像或原始文本数据输入到神经网络中?还是有其他方式向神经网络提供输入?在本节中,我们将学习计算机如何真正解释图像,以展示在处理图像时,神经网络到底能接收哪些输入(是的,神经网络在图像处理方面非常强大)。我们还将学习如何向神经网络输入原始文本数据。但在此之前,我们需要清楚了解常规的表格数据集是如何作为神经网络的输入的。因为表格数据集无处不在,形式包括 SQL 表、服务器日志等。
我们将使用以下玩具数据集进行此任务:
注意以下几点,关于这个玩具数据集:
-
它有两个预测变量,x1 和 x2,这些预测变量通常称为输入特征向量。
-
通常会将 x1 和 x2 分配给一个向量 X(稍后会详细讲解)。
-
响应变量是 y。
-
我们有 10 个实例(包含 x1、x2 和 y 属性),并将其分为两个类别:0 和 1。
-
给定 x1 和 x2,我们的(神经网络的)任务是预测 y,这本质上是一个分类任务。
当我们说神经网络预测某些内容时,我们的意思是它应该学习输入数据的潜在表示,这些表示最好能近似某个特定的函数(我们之前看过函数绘图的样子)。
现在让我们看看如何将这些数据作为输入传递给神经网络。由于我们的数据有两个预测变量(或两个输入向量),所以神经网络的输入层必须包含两个神经元。我们将使用以下神经网络架构来处理这个分类任务:
架构与我们之前看到的几乎相同,但在这个例子中,我们增加了一个输入特征向量。其他部分完全相同。
为了简化起见,我们不考虑在将数据输入网络之前可能需要的数据预处理。现在,让我们看看数据是如何与权重和偏置项结合,以及激活函数如何应用于它们。
在这种情况下,特征向量和响应变量(即 y)被神经网络分别处理,响应变量在网络的后期训练过程中使用。最重要的是,它被用来评估神经网络的表现。输入数据以矩阵形式组织,如下所示:
我们现在使用的神经网络架构是全连接架构,这意味着某一层中的所有神经元都与下一层中的所有神经元相连接。
权重矩阵定义如下:
现在,让我们暂时不关心权重值。权重矩阵的维度可以解释为以下内容:
-
行数等于特征向量的数量(在我们的案例中是 x1 和 x2)。
-
列的数量等于第一隐层中神经元的数量。
每个矩阵中权重值都与某些后缀和上标相关。如果我们将权重的一般形式表示为 ,那么它的解释如下:
-
l 表示权重来源的层。在这种情况下,我们刚刚看到的权重矩阵将与输入层相关联。
-
j 表示神经元在
中的位置,而 k 表示神经元在下一层中,值传播到的位置。
权重通常是随机初始化的,这为神经网络增加了 随机 特性。让我们随机初始化输入层的权重矩阵:
现在我们计算将要赋予神经网络第一隐层的值。计算方法如下:
第一个矩阵包含来自训练集的所有实例(不包括响应变量 y),第二个矩阵是我们刚定义的权重矩阵。这个乘法的结果存储在一个变量中,(这个变量可以命名为任何名称,上标表示它与网络的第一隐层相关)。
在将这些结果传送到下一层神经元之前,我们还有一步需要完成,在下一层神经元上将应用激活函数。sigmoid 激活函数和输入层的最终输出如下所示:
在这里,a^((1)) 是我们为下一层神经元得到的最终输出。请注意,sigmoid 函数应用于 矩阵的每个元素。最终的矩阵将具有 10 X 3 的维度,其中每一行对应训练集中的一个实例,每一列对应第一隐层中的一个神经元。
我们看到的整个计算没有包含我们最初提到的偏置项 b。实际上,这只是向图像中添加另一个维度的问题。在这种情况下,在我们对 矩阵的每个元素应用 sigmoid 函数之前,矩阵本身会变成如下所示:
在这个矩阵乘法过程之后,应用了 sigmoid 函数,输出会被传送到下一层的神经元,而这个过程会在每一层隐藏层和输出层中重复进行。随着进程的推进,我们应该从输出层获得。
Sigmoid 激活函数的输出值范围是 0 到 1,但我们处理的是二分类问题,并且我们只希望神经网络的最终输出是 0 或 1。我们可以通过一个小的调整来实现这一点。我们可以在神经网络的输出层定义一个阈值——对于小于 0.5 的值,它们应被标识为类别 0,而对于大于或等于 0.5 的值,它们应被标识为类别 1。请注意,这叫做前向传递或前向传播。
我们刚才看到的神经网络被称为前馈网络,在其学习过程中没有进一步的优化。但是等一下!这个网络到底学到了什么?实际上,神经网络通常学习的是权重和偏置项,以使最终输出尽可能准确。这一过程通过梯度下降和反向传播来实现。
梯度下降和反向传播
在我们开始了解梯度下降和反向传播在神经网络中的作用之前,让我们先了解一下什么是优化问题。
简而言之,优化问题对应于以下内容:
-
最小化某个成本
-
最大化某个利润
现在,让我们尝试将其映射到神经网络上。如果在从前馈神经网络获得输出后,我们发现其性能没有达到预期(这几乎是每次都会发生的情况),我们该如何提高神经网络的性能呢?答案就是梯度下降和反向传播。
我们将通过这两种技术来优化神经网络的学习过程。那么我们要优化什么呢?我们要最小化或最大化什么呢?我们需要一种特定类型的成本函数,我们将尝试最小化它。
我们将通过一个函数来定义成本。在我们为神经网络模型定义成本函数之前,我们必须先决定成本函数的参数。在我们的例子中,权重和偏置项是神经网络试图学习的函数参数,以便为我们提供准确的结果(请参见本节前面的信息框)。此外,我们还需要计算网络在每次训练步骤中所犯的损失。
对于二分类问题,广泛使用一种称为交叉熵的损失函数(对于二分类问题,它被称为二元交叉熵损失函数),我们也将使用它。那么,这个函数是什么样的呢?
在这里,y 表示给定实例的真实标签或地面真实值(记住训练集中的响应变量 y),而 表示由神经网络模型输出的结果。这个函数是凸函数,这非常适合使用像梯度下降法这样的凸优化器。
这也是我们没有选择一个更简单且非凸的损失函数的原因之一。(如果你不熟悉凸和非凸等术语,不必担心。)
我们现在有了损失函数。请记住,这只是针对整个数据集中的一个实例的函数,这不是我们将应用梯度下降法的函数。前述函数将帮助我们定义代价函数,最终我们会通过梯度下降法来优化它。让我们看看那个代价函数是什么样子。
在这里,w 和 b 是网络尝试学习的权重和偏置。字母 m 表示训练实例的数量,在本例中为 10。其余部分看起来很熟悉。我们将原始形式的函数 L() 放入其中,看看 J() 看起来是什么样子:
这个函数可能有点让人困惑,所以请慢慢理解,确保你能很好地理解它。
我们最终可以进入优化过程。广义而言,梯度下降法试图做以下几件事:
-
给我们一个点,使得代价函数最小化(这个点叫做最小值点)。
-
给我们正确的权重和偏置值,使得代价函数达到那个点。
为了可视化这一点,我们先来看一个简单的凸函数:
现在,假设我们从一个随机点开始,比如以下的点:
所以,右上角的点就是我们开始的点。由虚线箭头指示的点是我们希望到达的点。那么,我们如何通过简单的计算来实现这一点呢?
为了达到这一点,使用了以下更新规则:
在这里,我们正在对 J(w,b) 关于权重进行偏导数计算。我们进行偏导数计算是因为 J(w,b) 将 b 作为参数之一。𝝰 是加速该过程的学习率。这个更新规则会应用多次,以找到合适的权重值。那么偏置值呢?规则完全相同,只是方程发生了变化:
这些新的权重和偏置的分配本质上被称为反向传播,并且是与梯度下降一起完成的。在计算出新的权重和偏置值后,整个前向传播过程将被重复,直到神经网络模型能够很好地泛化。请注意,这些规则仅适用于单一实例,前提是该实例只有一个特征。对于包含多个特征的多个实例来说,做这件事可能会比较困难,因此我们将跳过这一部分,不过那些对完整版本感兴趣的人可以参考 Andrew Ng 的在线讲座。
我们已经涵盖了标准神经网络所需的基本单元,这一点并不容易。我们从定义神经元开始,最后讨论了反向传播(反向传播的技术术语)。我们已经为深度神经网络打下了基础。读者可能会想,这是不是我们刚刚学习的那个深度神经网络。正如安德里·布尔科夫在他的书《一百页机器学习书》中所说:
深度学习指的是训练具有两个以上非输出层的神经网络。...“深度学习”这一术语指的是使用现代的算法和数学工具训练神经网络,而与神经网络的深度无关。实际上,许多商业问题可以通过具有 2-3 个层次的神经网络来解决,这些层次位于输入层和输出层之间。
在接下来的章节中,我们将学习深度学习与浅层学习之间的区别。我们还将介绍两种不同类型的神经网络——卷积神经网络和循环神经网络。
不同类型的神经网络
到目前为止,我们已经了解了前馈神经网络的结构以及如何将反向传播和梯度下降等技术应用于它,以优化其训练过程。我们之前学习的二分类问题似乎太过简单和不切实际了,不是吗?
好吧,有很多问题是简单的神经网络模型能够很好地解决的。但随着问题复杂性的增加,对基本神经网络模型的改进变得必要。这些复杂问题包括物体检测、物体分类、图像说明生成、情感分析、假新闻分类、序列生成、语音翻译等等。像这些问题,单纯的神经网络模型是无法满足的。它需要一些架构上的改进才能解决这些问题。在这一部分,我们将学习两种最强大且广泛使用的神经网络模型——卷积神经网络和循环神经网络。正是这些神经网络模型支撑着当今深度学习令人惊叹的应用。
卷积神经网络
你有没有将朋友们的合照上传到 Facebook?如果有,你是否曾经想过,在上传完成后,Facebook 是如何自动检测出照片中的所有人脸的?简而言之,答案是卷积神经网络(CNNs)。
前馈网络通常由若干全连接层组成,而卷积神经网络(CNN)由若干卷积层以及其他类型的复杂层(包括全连接层)组成。这些全连接层通常位于网络的最后,并通常用于做预测。那么,预测的内容是什么呢?在图像处理和计算机视觉的背景下,预测任务可以涵盖多种使用场景,比如识别图像中给定对象的类型。但是,CNN 只适用于与图像相关的任务吗?CNN 是为图像处理任务(如目标检测、目标分类等)设计和提出的,但它也在许多文本处理任务中得到了应用。我们将要学习 CNN 在图像处理中的应用,因为 CNN 在图像处理和计算机视觉领域的奇迹使其最为人们熟知。不过,在我们深入讨论这个话题之前,了解如何将图像以数字的形式表示是非常有用的。
一张图像由大量的像素和维度组成——高度 x 宽度 x 深度。对于彩色图像,深度维度通常是 3,而对于灰度图像,维度是 1。让我们深入了解一下。请看下图:
前面的图像尺寸为 626 x 675 x 3,数字上,它只是一个矩阵。每个像素代表红色、绿色和蓝色的特定强度(根据 RGB 颜色系统)。该图像总共有 422,550 个像素(675 x 626)。
这些像素由红色、绿色和蓝色的三个值组成。现在,让我们看看一个像素(对应于 422,550 个像素矩阵中的第 20 行和第 100 列)在编码中的表现:
12, 24, 10
每个值对应于红色、绿色和蓝色的特定强度。为了便于理解 CNN,我们将查看一个维度较小的灰度图像。请记住,灰度图像中的每个像素值介于 0 和 255 之间,其中 0 对应黑色,255 对应白色。
以下是一个代表灰度图像的虚拟像素矩阵(我们将其称为图像矩阵):
在我们继续之前,先直观地思考一下,我们如何训练一个 CNN 来学习图像的潜在表示并使其执行某些任务。图像有一个特殊的固有属性:图像中包含相似类型信息的像素通常会彼此靠近。以标准人脸图像为例:表示头发的像素较暗,且在图像上彼此接近,而表示面部其他部分的像素通常较亮,并且也非常接近。不同面孔的强度可能有所不同,但你能理解这一点。我们可以利用图像中像素的空间关系,训练 CNN 来检测相似的像素以及它们之间产生的边缘,从而区分图像中的不同区域(在人脸图像中,头发、眉毛等之间有任意的边缘)。让我们看看如何实现这一点。
一个 CNN 通常包含以下组件:
-
卷积层
-
激活层
-
池化层
-
全连接层
在卷积神经网络(CNN)的核心是一个被称为卷积的操作(在计算机视觉和图像处理的文献中也称为交叉关系)。PyImageSearch 的 Adrian Rosebrock 如下描述了这一操作:
在深度学习中,图像卷积是两个矩阵的逐元素相乘,然后求和。
这段话告诉我们一个(图像)卷积操作是如何工作的。文中提到的矩阵是图像矩阵本身和另一个被称为卷积核的矩阵。原始图像矩阵可以大于卷积核矩阵,卷积操作是按从左到右、从上到下的方向在图像矩阵上进行的。以下是一个涉及前面虚拟矩阵和一个 2 x 2 大小的卷积核的卷积操作示例:
卷积核矩阵实际上充当了网络的权重矩阵,为了简化起见,我们暂时忽略偏置项。值得注意的是,我们最喜欢的图像滤镜(如锐化、模糊等)实际上就是对原始图像应用某些卷积操作后的输出。CNN 实际上学习这些滤镜(卷积核)值,从而能够最佳地捕捉图像的空间表示。这些值可以通过梯度下降和反向传播进一步优化。下图展示了对图像应用的四个卷积操作:
注意核是如何滑动的,以及如何计算卷积像素。但如果我们继续这样做,那么图像的原始维度就会丢失,这可能会导致信息丢失。为了防止这种情况,我们应用一种叫做填充(padding)的技术,以保持原始图像的维度。填充技术有很多种,例如复制填充、零填充、环绕填充等等。零填充在深度学习中非常流行。现在我们来看一下如何将零填充应用于原始图像矩阵,从而保持图像的原始维度:
零填充意味着像素值矩阵将在所有边缘用零填充,如前面的图像所示。
重要的是指导网络如何滑动图像矩阵。这是通过一个叫做步长(stride)的参数来控制的。步长的选择取决于数据集,并且在深度学习中,正确使用步长 2 是标准做法。让我们看看步长 1 与步长 2 的不同:
一个复杂的图像通常看起来像下面这样:
卷积图像在很大程度上取决于所使用的核。最终输出的矩阵会传递给激活函数,并且该函数会应用于矩阵的元素。卷积神经网络(CNN)中的另一个重要操作是池化,但我们现在先跳过这一部分。到目前为止,您应该对 CNN 如何在高层次上工作有了较好的理解,这对于继续阅读本书已经足够。如果你想更深入了解 CNN 的工作原理,可以参考这篇博客:www.pyimagesearch.com/2018/04/16/keras-and-convolutional-neural-networks-cnns/
。
循环神经网络
循环神经网络(RNNs)是另一种神经网络类型,非常擅长处理 NLP 任务——例如情感分析、序列预测、语音转文本翻译、语言翻译等等。考虑一个例子:你打开 Google 并开始搜索循环神经网络。当你开始输入一个词时,Google 会开始给出一系列建议,最有可能的是在你输入的字母后,搜索量最大的完整单词或短语。这是一个序列预测的例子,其中的任务是预测给定短语的下一个序列。
让我们再来看一个例子:给定一组包含一个空白的英语句子,你的任务是适当地填充这些空白,选择正确的单词。为了做到这一点,你需要使用你对英语语言的先前知识,并尽可能地利用上下文。为了使用之前遇到的信息,你需要用到记忆。那么,神经网络呢?传统的神经网络无法做到这一点,因为它们没有任何记忆。这正是 RNN 派上用场的地方。
我们需要回答的问题是,如何赋予神经网络记忆?一个完全天真的想法是做以下的事情:
-
将一个特定的序列输入到神经元中。
-
将神经元的输出再输入到该神经元中。
事实证明,这个想法并非那么天真,实际上它构成了 RNN 的基础。一个 RNN 的单层实际上看起来像下面这样:
循环看起来有点神秘。你可能已经在想每次循环迭代时会发生什么:
在之前的图示中,RNN(左侧的图形)被展开成三个简单的前馈网络。但这些展开的网络究竟在做什么呢?让我们现在来找找看。
让我们考虑一下序列预测任务。为了简单起见,我们将看看 RNN 如何学习预测下一个字母以完成一个单词。例如,如果我们用一组字母 {w, h, a, t} 来训练网络,依次输入字母 w, h 和 a 后,网络应该能够预测下一个字母是 t,这样就能形成有意义的单词“what”。就像我们之前看到的前馈网络一样,在 RNN 术语中,X 作为输入向量,这个向量也被称为网络的词汇表。在这种情况下,网络的词汇表是 {w, h, a, t}。
网络依次输入字母 w, h 和 a。我们尝试为字母指定索引:
-
→
-
→
-
→
这些索引被称为时间步(图中展示的 RNN 展开过程中的上标)。一个递归层会利用前一个时间步输入的内容,并结合当前时间步的函数进行操作。让我们一步一步地来看这个递归层是如何产生输出的。
将字母输入到网络中
在我们看到递归层如何产生输出之前,了解如何将字母集输入到网络中是很重要的。独热编码让我们以非常高效的方式做到这一点:
因此,在独热编码中,我们的输入向量/字母词汇就是四个 4 x 1 矩阵,每个矩阵表示一个特定字母。独热编码是这些任务的标准做法。此步骤实际上是数据预处理步骤。
初始化权重矩阵及其他
当涉及神经网络时,就会有权重。这是对的吗?但在我们开始处理 RNN 的权重之前,让我们先看一下它们具体在哪里需要。
在 RNN 中有两个不同的权重矩阵——一个用于输入神经元(记住,我们只通过神经元传递特征向量),一个用于递归神经元。RNN 中的特定状态是通过以下两个方程式产生的:
要理解第一个方程中的每个术语是什么意思,请参考下图(别担心,我们稍后会讨论第二个方程):
RNN 的第一次传递 是字母 w。我们将随机初始化方程(1)中出现的两个权重矩阵。假设矩阵
初始化后的样子如下:
矩阵是 3 x 4:
-
x = 3,因为我们在递归层中有三个递归神经元
-
h = 4,因为我们的词汇量是 4
矩阵 是一个 1 x 1 矩阵。我们假设它的值为 0.35028053。我们还在这里引入偏置项 b,它也是一个 1 x 1 的矩阵,值为 0.6161462。在下一步中,我们将把这些值结合起来并确定
的值。(我们稍后会处理第二个方程式。)
将权重矩阵组合起来
让我们首先确定 。
是一个 4 x 1 矩阵,表示我们之前定义的字母 w。这里适用矩阵乘法的标准规则:
现在我们将计算术语 。我们将很快看到偏置项的意义。由于 w 是我们输入到网络中的第一个字母,它没有任何前一个状态,因此,我们将取
作为一个 3 x 1 的零矩阵:
请注意,如果我们没有考虑偏置项,我们将得到一个全为零的矩阵。现在我们将按照方程(1)将这两个矩阵相加。这次加法的结果是一个 3 x 1 矩阵,并存储在 中(在此情况下是
):
根据公式(1),我们需要做的就是将激活函数应用于这个矩阵。
应用激活函数和最终输出
当涉及到 RNN 时,是一个很好的激活函数选择。所以,在应用
之后,矩阵如下所示:
我们已经得到了的结果。ht在下一个时间步长中充当
。接下来,我们将使用公式(2)计算
的值。我们需要另一个权重矩阵
(形状为 4 x 3),它是随机初始化的:
在应用第二个公式后,的值变成了一个 4 x 1 的矩阵:
现在,为了预测在* w之后可能出现的下一个字母(记住,我们所有的计算都是从字母w*开始的,并且我们仍然停留在 RNN 的第一次传递阶段),为了从给定的词汇中构建合适的单词,我们将对应用 softmax 函数。这样会输出一组来自词汇中每个字母的概率:
如果有人好奇想了解 softmax 函数是什么样子,可以阅读这篇非常有帮助的文章:bit.ly/softmaxfunc
。
所以,RNN 告诉我们,w之后最有可能的字母是。至此,我们完成了 RNN 的初始传递。作为练习,你可以尝试调整我们从这一传递中得到的ht值,并将其(与下一个字母h一起)应用于 RNN 的下一个传递,看看会发生什么。
现在,让我们来回答最重要的问题——网络在学习什么?再次回答,权重和偏置!你可能已经猜到了下一个句子。这些权重通过反向传播进一步优化。现在,这种反向传播与我们之前看到的有所不同。这种版本的反向传播被称为时间反向传播。我们将不会学习这个内容。在结束本节之前,让我们总结一下在 RNN 前向传播过程中执行的步骤(在对词汇进行独热编码之后):
-
随机初始化权重矩阵。
-
使用公式(1)计算
。
-
使用公式(2)计算
。
-
对
应用 softmax 函数,得到词汇中每个字母的概率。
值得了解的是,除了 CNN 和 RNN 外,还有其他类型的神经网络,如自编码器、生成对抗网络、胶囊网络等。在前两节中,我们详细了解了两种最强大的神经网络类型。但当我们谈论前沿的深度学习应用时,这些网络足够用吗?还是我们需要在这些基础上进行更多的增强?事实证明,尽管这些架构表现良好,但它们未能扩展,因此需要更复杂的架构。在接下来的章节中,我们将介绍一些这些专门的架构。
自从 第一章《揭开人工智能与机器学习基础的神秘面纱》以来,我们已经覆盖了不少理论内容。在接下来的几节中,我们将深入一些实际操作的例子。
探索 Jupyter Notebooks
在进行与深度学习相关的项目时,您必须处理大量各种类型的变量和不同维度的数组。此外,由于它们所包含的数据庞大且几乎在每一步后都会发生变化,我们需要一个工具来帮助我们观察每个步骤产生的输出,以便我们能够据此继续进行。Jupyter Notebook 就是这样一个工具。Jupyter Notebooks 因其简单性而闻名,并且其广泛的功能支持和平台兼容性使其成为目前开发深度学习解决方案的标准工具。它们受欢迎的原因可以通过考虑到多个顶级科技巨头都提供了自己版本的工具来理解,例如 Google Colaboratory 和 Microsoft Azure Notebooks。此外,流行的代码托管网站 GitHub 自 2016 年起便提供了 Jupyter Notebook 的本地渲染。
安装 Jupyter Notebook
让我们从安装 Jupyter Notebook 开始。
使用 pip 安装
如果您的系统已经安装了 Python,您可以从 pip
仓库安装 Jupyter 包,以快速开始使用 Jupyter Notebooks。
对于 Python 3,请使用以下命令:
python3 -m pip install --upgrade pip
python3 -m pip install jupyter
对于 Python 2,请使用以下命令:
python -m pip install --upgrade pip
python -m pip install jupyter
对于 Mac 用户,如果找不到 pip
安装,您可以下载最新的 Python 版本,其中已包含 pip
。
使用 Anaconda 安装
尽管可以通过 pip
安装 Jupyter 单个包,但强烈建议您安装 Anaconda 发行版,它会自动安装 Python、Jupyter 和机器学习与数据科学所需的其他几个包。Anaconda 使得处理各种包版本以及更新依赖包或相关包变得非常容易。
首先,从 www.anaconda.com/downloads
下载适合您系统和需求的 Anaconda 发行版,然后按照网站上给出的相应安装步骤进行安装。
验证安装
要检查 Jupyter 是否正确安装,请在命令提示符(Windows)或终端(Linux/Mac)中运行以下命令:
jupyter notebook
你将能够在终端(以下简称 Windows 上的命令提示符或 Linux/Mac 上的终端)看到一些日志输出。之后,你的默认浏览器将打开,并跳转到浏览器中的一个链接,链接内容类似于以下图像:
在 "Files" 标签页下,提供了一个基本的文件管理器,用户可以用它来创建、上传、重命名、删除和移动文件。
"Running" 标签页列出了所有当前运行的 Jupyter Notebook,用户可以从列表中关闭它们。
"Clusters" 标签页提供了所有可用 IPython 集群的概览。为了使用此功能,您需要为 Python 环境安装 IPython Parallel 扩展。
Jupyter Notebooks
默认情况下,Jupyter Notebook 的文件扩展名是 .ipynb
。当你在 Jupyter 提供的文件管理器中点击该类笔记本的名称时,屏幕上将显示一个类似以下的界面:
最上方的部分,你可以看到一个菜单栏、工具栏和笔记本标题,称为标题。标题的右侧,你可以看到笔记本执行的环境,当有任务正在运行时,环境语言名称旁的白色圆圈会变成灰色。
在标题下方是笔记本的主体,它由垂直堆叠的单元格组成。笔记本主体中的每个单元格可以是代码块、Markdown 单元格或原始单元格。代码单元格下方可以附加一个输出单元格,用户无法手动编辑该单元格。该单元格保存由其关联的代码单元格生成的输出。
在 Jupyter Notebook 中,键盘在不同的模式下行为不同,因此这些笔记本被称为模式化的。一个笔记本的单元格可以操作的模式有两种:命令模式和编辑模式。
当单元格处于命令模式时,它有一个灰色边框。在此模式下,单元格内容不能更改。在此模式下,键盘上的按键会映射到一些快捷键,用户可以用它们来修改单元格或整个笔记本。
在命令模式下,如果按下键盘上的Enter键,单元格模式会切换到编辑模式。在该模式下,可以更改单元格的内容,并可以调用浏览器中常见文本框的基本快捷键。
要退出编辑模式,用户可以按Esc键。要运行特定的单元格,用户需要输入Shift + Return,这将在每种情况下执行以下操作之一:
-
对于 Markdown 单元格,渲染后的 Markdown 会显示出来。
-
对于原始单元格,输入的原始文本将会显示。
-
对于代码单元格,代码将被执行,如果产生输出,将创建一个附加到代码单元格的输出单元格,并在那里显示输出。如果单元格中的代码要求输入,将显示一个输入字段,并且该单元格的代码执行将停顿,直到提供输入。
Jupyter 还允许使用其内置文本编辑器操作文本文件和 Python 脚本文件。还可以从 Jupyter 环境内调用系统终端。
设置基于深度学习的云环境
在我们开始设置基于云的深度学习环境之前,我们可能会想知道为什么我们需要它或者云基深度学习环境如何使我们受益。深度学习需要大量的数学计算。在神经网络的每一层,都有一个数学矩阵与另一个或多个这样的矩阵进行乘法运算。此外,每个数据点本身可以是一个向量而不是一个单一实体。现在,为了在多次重复训练过程中训练这样的深度学习模型,将需要大量时间,仅仅是因为涉及的数学运算数量。
使用 GPU 可能会更高效执行这些操作,因为 GPU 专门用于高速数学计算,然而,GPU 启用的机器成本高昂,可能不是每个人都能负担得起。此外,考虑到多个开发者在工作环境中使用同一软件,为团队中所有开发者购买 GPU 启用的机器可能是一个非常昂贵的选择。因此,GPU 启用的云计算环境的概念非常具有吸引力。
如今,公司越来越倾向于为其开发团队使用 GPU 启用的云环境,这可以为所有开发者创建一个共同的环境,并促进高速计算。
设置 AWS EC2 GPU 深度学习环境
在本节中,我们将学习如何在 AWS 上设置一个特定的深度学习实例。在您开始使用 AWS 之前,您需要在 AWS 控制台上创建一个帐户。为此,请按照以下步骤操作:
-
访问
console.aws.amazon.com
,您将看到登录/注册屏幕。 -
如果您尚未拥有 AWS 帐户,请点击“创建新的 AWS 帐户”并按照步骤创建一个帐户,这可能需要您输入借记/信用卡详细信息以启用帐单。
-
登录您的帐户后,在仪表板上,在“所有服务”部分点击 EC2,如下截图所示:
一旦您进入 AWS 控制台中的 EC2 管理页面,您将需要按照以下部分的步骤来为您的深度学习需求创建实例。
第一步:创建一个 EC2 GPU 启用实例
首先,选择 Ubuntu 16.04 或 18.04 LTS AMI:
然后,选择一个支持 GPU 的实例配置。g2.2xlarge
是一个适合初学者的深度学习环境配置:
接下来,配置所需的实例设置,或者保持默认设置并继续到存储步骤。此处,建议的磁盘大小为 30 GB。然后,您可以使用默认选项继续启动实例。
将 EC2 密钥对分配给您的实例,这样您就可以通过 SSH 从您的系统访问实例的终端。如果您将密钥对命名为 abc
,则名为 abc.pem
的文件会自动下载到浏览器的默认下载位置。
步骤 2:SSH 连接到您的 EC2 实例
在系统上打开终端,使用 cd
命令,导航到存储 abc.pem
文件的目录。
如果您不熟悉 cd
命令,假设您位于一个名为 Folder1
的文件夹中,文件夹内容如下:
Folder1 /
- Folder2
- Folder3
- File1.jpg
- File2.jpg
要访问名为 Folder2
的文件夹中的任何文件,您需要将工作目录切换到该文件夹。为此,您可以使用以下 cd
命令示例:
cd Folder2
请注意,只有在您已经进入 Folder1
文件夹时,此命令才有效,您可以使用类似的 cd
命令从系统中的任何位置进入该文件夹。
您可以使用以下命令查看 Linux 系统上任何命令的使用方法:
man <command>
例如,您可以使用以下内容:
man cd
现在,通过输入以下命令设置所需的 SSH 权限:
$ chmod 400 abc.pem
现在,要 SSH 连接到您的实例,您需要其公共 IP 或实例公共 DNS。例如,如果公共 IP 是 1.2.3.4
,则使用以下命令:
$ ssh -i abc.pem ubuntu@1.2.3.4
AWS 实例的公共 IP 可以在 AWS 控制台 EC2 管理页面中运行实例列表下方的详细信息面板中找到。
步骤 3:在 GPU 实例上安装 CUDA 驱动程序
首先,更新/安装 NVIDIA 显卡驱动:
$ sudo add-apt-repository ppa:graphics-drivers/ppa -y
$ sudo apt-get update
$ sudo apt-get install -y nvidia-xxx nvidia-settings
这里,xxx
可以替换为安装在实例上的显卡硬件版本,您可以在实例详细信息中找到该版本。
接下来,下载 CUDA deb 文件(此代码适用于撰写时的最新版本,2019 年 1 月):
$ wget https://developer.download.nvidia.com/compute/cuda/10.0/secure/Prod/local_installers/cuda-repo-ubuntu1804-10-0-local-10.0.130-410.48_1.0-1_amd64.deb
然后,继续执行以下命令:
$ sudo dpkg -i cuda-repo-ubuntu1804-10-0-local-10.0.130-410.48_1.0-1_amd64.deb
$ sudo apt-key add /var/cuda-repo-<version>/7fa2af80.pub
$ sudo apt-get update
$ sudo apt-get install -y cuda nvidia-cuda-toolkit
为了验证是否成功安装了所有内容,请运行以下命令:
$ nvidia-smi
$ nvcc -version
如果这两个命令都没有产生任何警告或错误的输出,那么安装成功。
步骤 4:安装 Anaconda Python 发行版
首先,下载 Anaconda 安装脚本:
$ wget https://repo.continuum.io/archive/Anaconda3-2018.12-Linux-x86_64.sh
接下来,将脚本设置为可执行:
$ chmod +x Anaconda*.sh
然后,运行安装脚本:
$ ./Anaconda3-2018.12-Linux-x86_64.sh
安装程序将询问一些选项。为了验证安装是否成功,使用以下命令:
$ python3
Python3 REPL 会加载到终端,并显示已安装的 Anaconda 版本的横幅。
步骤 5:运行 Jupyter
使用以下命令在实例上启动 Jupyter Notebook 服务器:
$ jupyter notebook
打开终端后,输出将包含一个 URL,你可以通过该 URL 访问运行在 EC2 GPU 实例上的 Jupyter Notebook。
在 Crestle 上进行深度学习
定制的深度学习环境在你需要对系统进行更大控制时很有用——比如当你希望第三方应用与深度学习模型一起工作时——但在其他情况下,你可能没有这样的需求,可能只想快速地在云端进行深度学习,并且希望能够协作。在这种情况下,支付 AWS g2.2xlarge
实例的费用会比仅支付计算时间或 GPU 时间的费用要高得多。
Crestle 是一个提供 GPU 支持的 Jupyter Notebook 在线服务,价格非常实惠。要开始使用 Crestle,请按照以下步骤操作:
-
登录到www.crestle.com。
-
点击 Sign Up 并填写出现的注册表单。
-
检查你的电子邮件,找到帐户确认链接。激活你的帐户并登录。
-
你将被带到仪表板,在那里你会看到一个按钮,写着“Start Jupyter”。你可以选择使用 GPU 或保持禁用。点击启用 GPU 选项的 Start Jupyter 按钮。
你将进入一个运行在云端的 Jupyter 环境,支持 GPU。虽然价格会随着时间的推移而变化,但截至 2020 年 1 月,它仍是互联网上最实惠的解决方案之一。
其他深度学习环境
除了前面提到的云端启用 GPU 的深度学习方式外,在某些情况下,你也可以选择使用其他平台。
Google Colaboratory 是一个免费提供的 Jupyter Notebook 服务,可以在colab.research.google.com
访问。Colaboratory 笔记本存储在用户的 Google Drive 上,因此存储限制为 15 GB。可以通过 Google Drive Python API 将大型数据集存储在 Google Drive 上,并在项目中使用这些数据集。默认情况下,Colaboratory 禁用 GPU,需要手动开启。
Kaggle 是另一个专门为数据科学竞赛而建立的平台。它提供了一个类似 Jupyter Notebook 的环境,称为kernel。每个 kernel 都提供大量的内存和免费的 GPU 计算能力。然而,Kaggle 的存储限制比 Google Colaboratory 更严格,因此当计算密集型任务但数据和输出不大时,它是一个有效的选择。
探索 NumPy 和 pandas
NumPy 和 pandas 是几乎所有 Python 语言中与数据科学相关的库的基础。虽然 pandas 是建立在 NumPy 之上的,但 NumPy 本身是将 Python 包装在高性能的 C 代码上,从而在 Python 中提供比纯 Python 更优秀的数学计算能力。
几乎所有使用 Python 开发的深度学习软件,都或多或少依赖于 NumPy 和 pandas。因此,深入理解这两个库及其提供的特性非常重要。
NumPy
NumPy 是 Numerical Python(数值计算 Python)的缩写。普通的 Python 缺乏数组的实现,而数组是用于开发机器学习模型的数学矩阵的近似对象。NumPy 为 Python 提供了对多维数组和高性能计算特性的支持。可以通过以下导入语句将其包含到任何 Python 代码中:
import numpy as np
np
是导入 NumPy 时常用的约定。
NumPy 数组
在 NumPy 中创建数组有多种方法。以下是一些显著的方法:
np.array
:将 Python 列表转换为 NumPy 数组:
np.ones
或np.zeros
:创建一个全为 1 或全为 0 的 NumPy 数组:
np.random.rand
:生成一个随机数数组:
np.eye
:生成给定方阵维度的单位矩阵:
现在让我们来看一下基本的 NumPy 数组操作。
基本的 NumPy 数组操作
NumPy 数组是 Python 对数学矩阵的类比,因此它们支持所有基本类型的算术操作,如加法、减法、除法和乘法。
让我们声明两个 NumPy 数组并将其存储为 array1
和 array2
:
array1 = np.array([[10,20,30], [40, 50, 60], [70, 80, 90]])
array2 = np.array([[90, 80, 70], [60, 50, 40], [30, 20, 10]])
现在让我们看一些关于这些数组的基本算术操作的例子:
- 加法:
- 减法:
- 乘法:
- 除法:
现在让我们将 NumPy 数组与 Python 列表进行比较。
NumPy 数组与 Python 列表的比较
现在让我们看看 NumPy 数组相较于 Python 列表的优势。
对多个行和列进行数组切片
虽然在 Python 中不能以切片的方式对列表中的列表进行操作,从而选择特定的行和列,但 NumPy 数组的切片操作遵循以下语法:
Array [ rowStartIndex : rowEndIndex, columnStartIndex : columnEndIndex ]
这是一个例子:
在前面的例子中,我们能够在 NumPy 数组 a
中选择两行及该行的所有元素。
切片赋值
虽然无法给 Python 列表的切片分配值,但 NumPy 允许给 NumPy 数组分配值。例如,要将 4 分配给 NumPy 一维数组的第三到第五个元素,我们可以使用以下方法:
arr[2:5] = 4
接下来,我们将学习 pandas。
pandas
pandas 构建于 NumPy 之上,是 Python 中用于数据科学的最广泛使用的库之一。它提供了高性能的数据结构和数据分析方法。pandas 提供了一个内存中的二维表格对象,称为 DataFrame,DataFrame 又由一个一维的类似数组的结构(称为 series)构成。
每个 pandas DataFrame 都是一个类似电子表格的表格,具有行标签和列头。可以执行基于行或列的操作,或者同时进行两者。pandas 与 matplotlib 强集成,提供了多种直观的数据可视化功能,这些功能在制作演示文稿或进行探索性数据分析时非常有用。
要将 pandas 导入 Python 项目,使用以下代码:
import pandas as pd
这里,pd
是导入 pandas 时常用的别名。
pandas 提供了以下数据结构:
-
Series:一维数组或向量,类似于表格中的一列
-
数据框(DataFrames):二维表格,具有表头和行标签
-
面板(Panels):一个 DataFrame 的字典,就像一个包含多个表的 MySQL 数据库
可以使用 pd.Series( )
方法创建 pandas series,而使用 pd.DataFrame( )
方法创建 DataFrame——例如,在以下代码中,我们使用多个 series 对象创建一个 pandas DataFrame 对象:
import pandas as pd
employees = pd.DataFrame({ "weight": pd.Series([60, 80, 100],index=["Ram", "Sam", "Max"]),"dob": pd.Series([1990, 1970, 1991], index=["Ram", "Max", "Sam"], name="year"),"hobby": pd.Series(["Reading", "Singing"], index=["Ram", "Max"])})
employees
上述代码的输出结果如下:
pandas DataFrame 中一些最重要的方法如下:
-
head(n)
或tail(n)
:显示 DataFrame 的前 n 行或后 n 行。 -
info( )
:显示 DataFrame 中所有列的信息、维度和列的数据类型。 -
describe( )
:显示 DataFrame 中每一列的便捷聚合和统计信息。非数值列会被省略。
总结
本章涵盖了许多不同的内容。我们从学习神经网络的基础开始,然后逐步深入。我们学习了今天使用的两种最强大的神经网络类型——CNN 和 RNN,并且在高层次上了解了它们,但没有跳过它们的基础单元。我们了解到,随着神经网络复杂度的增加,它需要大量的计算能力,而标准计算机可能无法满足这一需求,我们还看到如何通过使用两个不同的服务提供商——AWS 和 Crestle,来配置深度学习开发环境,从而克服这一问题。我们探索了 Jupyter Notebooks,这是一款用于执行深度学习任务的强大工具。我们学习了两个非常流行的 Python 库——NumPy 和 pandas 的使用。这两个库在执行深度学习任务时被广泛使用。
在下一章,我们将构建应用程序并集成深度学习,使其具有智能表现。但在此之前,了解本章中涵盖的基础知识对我们来说非常重要。现在我们已经掌握了必要的基础,准备好继续进入下一章。
第五章:创建你的第一个深度学习 web 应用
在了解神经网络及其在实际项目中的设置后,下一步自然是开发一个基于 web 的深度学习应用。本章将致力于创建一个完整的 web 应用——尽管是一个非常简单的应用——它通过一种非常简单的方式展示了如何将深度学习集成到应用中。
本章将介绍一些本书中将使用的术语,因此即使是那些已经对深度学习 web 应用有基本理解的读者,也建议阅读此章,以便能理解未来章节中使用的术语。我们将首先构建一个深度学习 web 应用,并学习如何理解数据集。接下来,我们将使用 Python 实现一个简单的神经网络,并创建一个与服务器端 Python 配合工作的 Flask API。
本章将涵盖以下主题:
-
构建深度学习 web 应用
-
理解数据集
-
使用 Python 实现一个简单的神经网络
-
创建一个与服务器端 Python 配合工作的 Flask API
-
使用 cURL 和 Flask 的 web 客户端
-
改进深度学习后端
技术要求
你可以访问本章中使用的代码,链接在此:github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-web/tree/master/Chapter3
。
本章你将需要以下内容:
-
Python 3.6+
-
Flask 1.1.0+
-
TensorFlow 2.0+
构建深度学习 web 应用
解决拼图问题时,重要的是各个部分要匹配,而不是强行拼接。同样,在开发软件解决方案时,解决方案的各个部分必须无缝协作,它们的交互必须易于理解。好的软件需要合理的规划。因此,给软件提供一个坚实的结构对于其长期使用和未来的易于维护至关重要。
在我们开始创建第一个可以在 web 上运行的深度学习应用之前,我们必须规划出解决方案的蓝图,牢记我们希望解决的问题及其对应的解决方案。这就像我们在开发网站时规划身份验证系统或从一个页面传递表单值到另一个页面一样。
一个通用的深度学习 web 解决方案需要以下组件:
-
一个可以存储数据并响应查询的服务器
-
一个可以使用存储的数据并处理它,以生成基于深度学习的查询响应的系统
-
一个可以向服务器发送数据以进行存储、发送带有新数据的查询,并最终接受并使用服务器在查询深度学习系统后返回的响应的客户端
让我们尝试通过图示来可视化这个结构。
一个通用深度学习 web 应用的结构图
以下图示描绘了网页客户端、网页服务器和深度学习模型之间的交互:
我们将创建三个软件部分——客户端、服务器和深度学习模型——它们将共同工作。为此,客户端将向服务器发出 HTTP 请求,服务器将返回从单独训练的深度学习模型中提取的输出。该模型可能会在响应客户端 HTTP 请求的服务器文件中执行,也可能不会。在大多数情况下,深度学习模型与处理 HTTP 请求的文件是分开的。
在本章中提供的示例中,我们将分别在不同文件中呈现服务器、客户端和深度学习模型。我们的客户端将向服务器发送简单的 HTTP 请求,例如页面加载请求或 GET
请求 URL,这些请求将根据传入的查询产生深度学习模型的输出。然而,客户端通过 REST API 与服务器通信是非常常见的做法。
现在让我们继续了解我们的应用程序将要处理的数据集。
理解数据集
为了在执行时间和数据空间上产生最佳结果,并使用最有效的代码,我们必须充分理解正在使用的数据集。我们在这里使用的数据集可能是最流行的手写数字图像神经网络数据集——MNIST 数据集。
MNIST 手写数字数据集
该数据集由 Yann LeCun、Corinna Cortes 和 Christopher J.C. Burges 组成的团队创建。它是一个包含手写数字图像的大型数据集,包含 60,000 个训练样本和 10,000 个测试样本。该数据集可以在 yann.lecun.com/exdb/mnist/
上公开下载,文件以四个 .gz
压缩文件的形式提供。
这四个文件如下:
-
train-images-idx3-ubyte.gz
:训练集图像。这些图像将用于训练神经网络分类器。 -
train-labels-idx1-ubyte.gz
:训练集标签。训练集中的每个图像都有一个关联标签,该标签是该图像中可见的相应数字。 -
t10k-images-idx3-ubyte.gz
:测试集图像。我们将使用这些图像来测试神经网络的预测准确性。 -
t10k-labels-idx1-ubyte.gz
:测试集图像的标签。当我们的神经网络对测试集进行预测时,我们将与这些值进行比较,以检查结果。
由于其自定义格式,数据集中的图像无法直接查看。开发者在处理该数据集时需要自己创建一个简单的图像查看器。完成此操作后,您将能够看到图像,其样子大致如下:
让我们更深入地讨论这些图像。正如你所看到的,它们在两个轴上的像素数略超过 25。准确来说,图像的尺寸是 28 x 28 像素。由于图像是灰度的,因此可以将它们存储在一个单层的 28 x 28 矩阵中。因此,我们总共有 784 个值,范围从 0 到 1,其中 0 表示完全黑的像素,1 表示白色像素。范围内的任何数值表示不同深浅的黑色。在 MNIST 数据集中,这些图像以一个扁平的 784 个浮点数组的形式存在。为了查看这些图像,你需要将单维数组转换为 28 x 28 的二维数组,然后使用任何自定义开发或公开可用的工具(例如 Matplotlib 或 Pillow 库)绘制图像。
让我们在接下来的章节中讨论这个方法。
探索数据集
让我们从 MNIST 数据集网页下载这四个文件,网址为:yann.lecun.com/exdb/mnist
。下载完成后,解压所有文件,你应该会看到类似以下名称的文件夹:
-
train-images.idx3-ubyte
-
train-labels.idx1-ubyte
-
t10k-images.idx3-ubyte
-
t10k-labels.idx1-ubyte
将这些文件保存在你的工作目录中。接下来,我们将创建一个 Jupyter 笔记本,对我们提取的数据集文件进行探索性数据分析(EDA)。
在浏览器中打开你的 Jupyter Notebook 环境,并创建一个新的 Python 笔记本。首先,导入必要的模块:
import numpy as np
import matplotlib.pyplot as plt
上面的代码行导入了numpy
模块和matplotlib.pyplot
模块。numpy
模块提供了 Python 中的高性能数学函数,而matplotlib.pyplot
模块提供了一个简单的接口,用于绘制和可视化图表和图像。为了在 Jupyter 笔记本中查看此库的所有输出,请添加以下代码行:
%matplotlib inline
如果你使用的是 Windows 系统,要解压.gz
文件,可以使用 7-zip 软件,这是一个出色的压缩/解压工具,可以免费下载,网址是:www.7-zip.org
。
创建读取图像文件的函数
如前所述,无法直接查看下载的图像文件中的图像。因此,我们将创建一个 Python 函数,matplotlib
模块将使用该函数显示文件中的图像:
def loadImageFile(fileimage):
f = open(fileimage, "rb")
f.read(16)
pixels = 28*28
images_arr = []
while True:
try:
img = []
for j in range(pixels):
pix = ord(f.read(1))
img.append(pix / 255)
images_arr.append(img)
except:
break
f.close()
image_sets = np.array(images_arr)
return image_sets
前面的loadImageFile
函数接受一个参数,即包含图像的文件名称。在我们下载的文件夹中,有两个这样的文件:train-images-idx3-ubyte
和t10k-images-idx3-ubyte
。前面函数的输出是一个numpy
数组,包含图像数据。我们可以将结果存储在一个 Python 变量中,如下所示:
test_images = loadImageFile("t10k-images-idx3-ubyte")
现在,为了查看变量中保存着numpy
图像数组的图像,我们可以定义另一个函数,该函数以 784 个浮点数的单个图像像素数组作为输入,并将它们绘制成一幅图像。该函数定义如下:
def gen_image(arr):
two_d = (np.reshape(arr, (28, 28)) * 255).astype(np.uint8)
plt.imshow(two_d, interpolation='nearest', cmap='gray')
plt.show()
return
现在,假设我们想显示测试图像集中的第一张图像;因为我们已经将numpy
图像数组存储在test_images
变量中,我们可以运行以下代码:
gen_image(test_images[0])
我们能够看到以下输出:
现在我们能够查看图像后,可以继续构建一个函数,以允许我们从标签中提取相应的数字。
创建读取标签文件的函数
在 MNIST 数据集中,我们有两个标签文件可用:train-labels-idx1-ubyte
和t10k-labels-idx1-ubyte
。为了查看这些文件,我们可以使用下面的函数,该函数以文件名作为参数输入,并生成一个独热编码标签的数组:
def loadLabelFile(filelabel):
f = open(filelabel, "rb")
f.read(8)
labels_arr = []
while True:
row = [0 for x in range(10)]
try:
label = ord(f.read(1))
row[label] = 1
labels_arr.append(row)
except:
break
f.close()
label_sets = np.array(labels_arr)
return label_sets
此函数返回一个numpy
数组,其中包含独热编码的标签,维度为数据集中的样本数乘以 10。让我们观察单个条目,以了解独热编码的性质。运行以下代码,从测试集的第一个样本中打印独热编码的标签集:
test_labels = loadLabelFile("t10k-labels-idx1-ubyte")
print(test_labels[0])
我们得到以下输出:
[0 0 0 0 0 0 0 1 0 0]
通过注意到第七个索引处的数字为1
,我们可以理解测试数据集中第一幅图像的标签为7
。
数据集摘要
在对可用数据集进行非常简明的探索后,我们得出以下结果。
训练数据集包含 60,000 张图像,维度为 60,000 x 784,每幅图像为 28 x 28 像素。各个数字的样本分布如下:
数字 | 样本数量 | 数字 | 样本数量 |
---|---|---|---|
0 | 5,923 | 5 | 5,421 |
1 | 6,742 | 6 | 5,918 |
2 | 5,958 | 7 | 6,265 |
3 | 6,131 | 8 | 5,851 |
4 | 5,842 | 9 | 5,949 |
请注意,数字5
的样本数量比数字1
少。因此,模型如果训练不够精细,可能会在识别数字5
时犯错。
标签总数摘要告诉我们,所有 60,000 个样本都有相应的标签,并且没有缺少任何标签。
类似地,测试数据集中有 10,000 张图像和标签,样本数量的分布如下:
数字 | 样本数量 | 数字 | 样本数量 |
---|---|---|---|
0 | 980 | 5 | 892 |
1 | 1,135 | 6 | 958 |
2 | 1,032 | 7 | 1,028 |
3 | 1,010 | 8 | 974 |
4 | 982 | 9 | 1,009 |
测试数据集中样本数分布相当均匀。
使用 Python 实现一个简单的神经网络
在进行了一些基本的数据分析后,我们可以开始用 Python 编写我们的第一个神经网络。你可以在第二章中复习神经网络的概念,使用 Python 入门深度学习,然后继续前进。我们现在将创建一个卷积神经网络(CNN),该网络将预测手写数字标签。
我们首先创建一个新的 Jupyter notebook。按照惯例,你可以将其命名为 Model.ipynb
。这个 notebook 将用于开发一个已保存版本的深度学习模型,之后会将其放入一个脚本中以生成预测。
导入所需的模块
Model.ipynb
中将需要的模块如下导入:
import numpy as np
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Activation
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
from keras.layers.normalization import BatchNormalization
keras
模块用于快速实现具有 TensorFlow 后端的高性能神经网络。我们在前面的章节中讨论过 Keras。要安装 Keras,可以使用以下命令:
pip3 install keras
上述命令将安装 Keras。
重用我们加载图像和标签文件的函数
记得我们在探索数据集时创建的 loadImageFile
和 loadLabelFile
函数吗?我们将再次需要它们,因此我们将把这些函数复制到此 notebook 中。
它们共同生成两个代码单元,每个函数各一个:
-
loadImageFile()
方法 -
loadLabelFile()
方法
在新的代码单元中,我们创建 loadImageFile()
函数:
def loadImageFile(fileimage):
f = open(fileimage, "rb")
f.read(16)
pixels = 28*28
images_arr = []
while True:
try:
img = []
for j in range(pixels):
pix = ord(f.read(1))
img.append(pix / 255)
images_arr.append(img)
except:
break
f.close()
image_sets = np.array(images_arr)
return image_sets
在另一个新的代码单元中,创建 loadLabelFile()
函数:
def loadLabelFile(filelabel):
f = open(filelabel, "rb")
f.read(8)
labels_arr = []
while True:
row = [0 for x in range(10)]
try:
label = ord(f.read(1))
row[label] = 1
labels_arr.append(row)
except:
break
f.close()
label_sets = np.array(labels_arr)
return label_sets
然后,我们可以通过以下代码行将图像和标签文件导入为 numpy
数组:
train_images = loadImageFile("train-images-idx3-ubyte")
train_labels = loadLabelFile("train-labels-idx1-ubyte")
test_images = loadImageFile("t10k-images-dx3-ubyte")
test_labels = loadLabelFile("t10k-labels-idx1-ubyte")
这会创建 train_images
、train_labels
、test_images
和 test_labels
的 NumPy 数组。我们可以观察它们的形状,train_images
的输出如下:
(60000, 784)
接下来,我们将学习如何重塑数组以便使用 Keras 进行处理。
为 Keras 处理重塑数组
当前的图像数组形状不适合 Keras。我们必须将图像数组转换为形状 (60000, 28, 28, 1)
和 (10000, 28, 28, 1)
。
为此,我们使用以下代码行:
x_train = train_images.reshape(train_images.shape[0], 28, 28, 1)
x_test = test_images.reshape(test_images.shape[0], 28, 28, 1)
现在,如果我们观察 x_train
的形状,我们将得到如下输出:
(60000, 28, 28, 1)
我们无需对标签数组进行任何更改,因此直接将其赋值给 y_train
和 y_test
:
y_train = train_labels
y_test = test_labels
接下来,我们将使用 Keras 创建一个神经网络。
使用 Keras 创建神经网络
现在,我们准备好继续创建神经网络:
- 我们将首先在 Keras 中创建一个
Sequential
神经网络模型:
model = Sequential()
- 要向网络中添加一个神经元层,我们使用以下代码:
model.add(Conv2D(32, (3, 3), input_shape=(28,28,1)))
这将向网络中添加一个二维卷积神经元层,输入形状与图像的形状相同。
- 现在,让我们添加激活层,并将
relu
作为激活函数:
model.add(Activation('relu'))
- 添加激活层后,我们可以执行批量归一化。在训练过程中,数据会通过多个计算层,可能会变得过大或过小。这被称为协方差偏移,而批量归一化有助于将数据重新调整到中心区域。这有助于神经网络更快地训练:
BatchNormalization(axis=-1)
- 现在让我们在模型中添加更多的隐藏层:
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
BatchNormalization(axis=-1)
model.add(Conv2D(64,(3, 3)))
model.add(Activation('relu'))
BatchNormalization(axis=-1)
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten())
BatchNormalization()
model.add(Dense(512))
model.add(Activation('relu'))
BatchNormalization()
model.add(Dropout(0.2))
- 在神经网络的最后一层,我们需要输出
10
个值,采用独热编码(one-hot encoding)形式,以表示预测的数字。为此,我们添加了最后一层10
个神经元。这将包含0
到1
之间的 10 个值:
model.add(Dense(10))
- 最后,为了将这 10 个浮动值转换为独热编码,我们使用
softmax
激活函数:
model.add(Activation('softmax'))
现在,让我们编译并训练 Keras 神经网络。
编译和训练 Keras 神经网络
我们现在准备好编译并训练神经网络。要编译神经网络,我们使用以下代码:
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adam(),
metrics=['accuracy'])
在我们模型中(在前面的代码块中已编译),我们设置了分类交叉熵作为loss
函数;使用的优化器函数是Adam
优化器,评估指标是accuracy
。
然后,我们使用 Keras 模型对象的fit()
方法来训练神经网络:
model.fit(x_train, y_train,
batch_size=100,
epochs=10,
verbose=2,
validation_split=0.2)
建议将训练数据进一步拆分为验证数据和训练数据,同时保持测试集不变,但对于这个数据集来说,这样是可以的。
训练完成,共进行了 10 个批次,每批次包含 100 个样本。
评估和存储模型
训练完模型后,我们现在准备好评估它的准确性。为此,我们将使用以下代码:
score = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
我们将获得以下输出,基于之前的代码:
我们得到了 99%的准确率,这是一个非常好的准确率分数。现在,我们可以保存模型,它将在未来通过网页门户用于对用户输入进行预测。我们将把模型分为两部分——模型结构和模型权重。为了保存结构,我们将使用 JSON 格式,如下所示:
model_json = model.to_json()
with open("model.json", "w") as json_file:
json_file.write(model_json)
现在,为了保存 Keras 模型的权重,我们使用save_weights()
方法来保存该对象:
model.save_weights('weights.h5')
接下来,我们将创建一个 Flask API,以便与服务器端的 Python 协作。
创建一个 Flask API,以便与服务器端的 Python 协作
我们已经完成了深度学习模型,并将其结构存储在model.json
文件中,将模型的权重存储在weights.h5
文件中。现在我们准备将模型数据封装成 API,以便通过GET
或POST
方法将模型暴露给基于 Web 的调用。这里,我们将讨论POST
方法。让我们从服务器端的必要设置开始。
设置环境
在服务器中,我们将需要 Flask 模块——它将处理服务请求——而这些请求将运行需要 Keras(以及 TensorFlow)、NumPy 和许多其他模块的代码。为了快速设置我们项目的环境,我们按照以下步骤进行操作:
-
安装 Anaconda。
-
安装 TensorFlow 和 Keras。
-
安装 Pillow。
-
安装 Flask。
你可以参考以下命令块来安装 TensorFlow、Keras、Pillow 和 Flask:
pip3 install tensorflow keras pillow flask
我们现在可以开始开发我们的 API。
上传模型结构和权重
模型结构文件model.json
和权重文件weights.h5
需要存在于工作目录中。你可以将这些文件复制到一个新文件夹中,比如flask_api
,或者如果使用远程服务器,可以将它们上传到正确的路径。
创建我们的第一个 Flask 服务器
在工作目录中创建一个新文件,命名为flask_app.py
。这个文件将处理所有发送到服务器的请求。将以下代码放入该文件:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello World!"
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
前面的代码首先将必要的模块导入到脚本中。然后,它将应用程序设置为 Flask 服务器对象,并定义index
函数,指示其处理所有发送到"/"
地址的请求,无论请求类型如何。在脚本的末尾,使用 Flask 对象app
的run()
方法将脚本绑定到系统的指定端口。
我们现在可以部署这个简单的Hello World Flask 服务器了。我们在终端中运行以下命令:
python flask_app.py
现在,当我们在浏览器中打开http://localhost/
网址时,我们看到一个页面,展示了Hello World。index
函数处理发送到服务器根目录的请求,因为它的路由设置为"/"
。接下来,我们将扩展这个示例,创建一个可以专门处理预测请求的 API。
导入必要的模块
在前面的示例中,我们将扩展flask import
语句,导入一个额外的方法request
,该方法将允许我们处理发送到服务器的POST
请求。该行代码如下所示:
from flask import Flask, request
然后,我们导入读取和存储图像所需的模块。同时,还导入了numpy
模块,如下所示:
from scipy.misc import imread, imresize
import numpy as np
最后,我们导入 Keras 模块的model_from_json()
方法来加载保存的模型文件。然后,我们导入tensorflow
,因为 Keras 依赖于它来执行:
from keras.models import model_from_json
import tensorflow as tf
接下来,我们将数据加载到脚本运行时。
加载数据到脚本运行时并设置模型
一旦我们导入了必要的模块,就会加载保存的模型 JSON 和权重,代码如下所示:
json_file = open('model.json','r')
model_json = json_file.read()
json_file.close()
model = model_from_json(model_json)
model.load_weights("weights.h5")
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
graph = tf.get_default_graph()
请注意,我们还为会话提前创建了一个默认的graph
项。该项是在模型训练期间隐式创建的,但不会保存在保存的model
和weights
文件中,因此我们必须在此处显式创建它。
设置应用程序和索引函数
现在,我们将app
变量设置为 Flask 对象,并将"/"
路由设置为由index
函数处理,该函数实际上不会产生任何有意义的输出。这是因为我们将使用/predict
路由来提供我们的预测 API,如下所示:
app = Flask(__name__)
@app.route('/')
def index():
return "Oops, nothing here!"
我们将在下一节中介绍图像转换功能。
转换图像功能
有时,如果用户使用合适的设置发起图像 POST
请求,我们可能会收到 base64
编码的字符串形式的图像。我们可以创建一个函数来处理它:
import re
import base64
def stringToImage(img):
imgstr = re.search(r'base64,(.*)', str(img)).group(1)
with open('image.png', 'wb') as output:
output.write(base64.b64decode(imgstr))
我们使用 re
模块进行正则表达式判断,以确定传入的数据是否为 base64
字符串形式。需要使用 base64
模块解码字符串,然后将文件保存为 image.png
。
预测 API
现在,让我们定义 /predict
路由,这将是我们的 API,用于响应预测的数字:
@app.route('/predict/', methods=['POST'])
def predict():
global model, graph
imgData = request.get_data()
try:
stringToImage(imgData)
except:
f = request.files['img']
f.save('image.png')
x = imread('image.png', mode='L')
x = imresize(x, (28, 28))
x = x.reshape(1, 28, 28, 1)
with graph.as_default():
prediction = model.predict(x)
response = np.argmax(prediction, axis=1)
return str(response[0])
在这里,predict()
函数接收一个 POST
方法的输入,检查传入文件的格式,然后将其保存为名为 image.png
的文件。接着,图像被读取到程序中并调整为 28 x 28 的尺寸。接下来,图像数组被重塑,以便可以输入到 Keras 模型进行预测。然后,我们使用 Keras 模型的 predict()
方法,获得一个一热编码的输出,其中预测数字的索引被设置为 1
,其余部分保持为 0
。我们确定数字并将其发送到 API 的输出。
现在,我们必须在文件的末尾添加代码,将服务器绑定到一个端口并设置所需的配置:
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
app.run(debug=True)
我们设置了 debug=True
参数,以便在服务器的控制台中查看是否发生任何错误。在开发过程中,启用此选项总是个好主意,但在生产环境中,这行代码可以跳过。
在我们运行应用程序之前的最后一步是更新 '/'
路由的代码。每当有人访问这个路由时,我们将加载我们创建的 index.html
文件,如下所示:
@app.route('/')
def index():
return render_template("index.html")
我们现在已经准备好启动服务器并检查它是否正常工作。我们使用之前使用的相同命令来启动服务器:
python flask_app.py
上述命令将启动服务器。
通过 cURL 使用 API 并使用 Flask 创建一个 Web 客户端
在我们的服务器运行时,我们可以向其发送 POST
请求,包含图像内容,并期望在输出中得到一个预测的数字。测试任何 API 而不使用第三方工具的两种方法如下:
-
使用 cURL。
-
开发一个客户端来调用 API。
我们将涵盖这两种方法。
通过 cURL 使用 API
在我们开发客户端发送 POST
请求到 API 服务器之前,让我们通过 cURL 测试 API,cURL 是一个命令行工具,用于模拟对 URL 的 GET
和 POST
请求。
在终端或命令提示符中使用以下命令向您的预测 API 发出 curl
请求:
curl -X POST -F img=@"path_to_file" http://localhost/predict/
这里,-F
标志用于表示 POST
请求将包含文件。将包含文件的 POST
变量名为 img
,path_to_file
应该替换为您希望发送到服务器的文件的完整路径,该文件用于进行预测。
让我们通过一个示例来看看 API 是如何工作的。
假设我们有一个名为 self2.png
,尺寸为 275 x 275 的图像:
显然,服务器端的图像尺寸必须进行调整。为了发出请求,我们使用以下命令:
API 的输出是一个整数——2
。因此,API 成功运行。
为 API 创建一个简单的网页客户端
我们现在将创建一个简单的网页客户端来调用 API。为此,我们必须修改当前的代码。在flask_app.py
中,首先修改 Flask 的import
语句,以便将其扩展到另一个模块——render_template
,如下所示:
from flask import Flask, request, render_template
现在,我们在工作目录中创建一个文件夹templates
,并在其中添加一个文件index.html
,文件内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<title>MNIST CNN</title>
</head>
<body>
<h1>MNIST Handwritten Digits Prediction</h1>
<form>
<input type="file" name="img"></input>
<input type="submit"></input>
</form>
<hr>
<h3>Prediction: <span id="result"></span></h3>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src="img/{{ url_for('static',filename='index.js') }}"></script>
</body>
</html>
本质上,我们在这里所做的只是创建一个表单,其中有一个类型为文件的输入元素,命名为img
。然后,我们将 jQuery 添加到页面,并创建一个指向静态文件index.js
的链接,该文件在服务器的static
文件夹中提供。
让我们创建index.js
文件。首先,在根目录下创建一个文件夹static
,然后创建一个新文件index.js
,并写入以下代码:
$("form").submit(function(evt){
evt.preventDefault();
var formData = new FormData($(this)[0]);
$.ajax({
url: '/predict/',
type: 'POST',
data: formData,
async: false,
cache: false,
contentType: false,
enctype: 'multipart/form-data',
processData: false,
success: function (response) {
$('#result').empty().append(response);
}
});
return false;
});
上面的 jQuery 代码向/predict/
路由发送一个POST
请求,然后更新页面上的result
区域,显示从服务器返回的值。
让我们在这个网页客户端上进行一次示例运行。首先,我们需要重新启动 Flask 服务器。然后,我们在浏览器中打开http://localhost/
,就会看到如下的网页:
假设我们选择一个名为mnist7.png
的文件,它本质上是测试数据集中第一张图片,长这样:
预期的输出是7
。点击提交后,我们在页面上获得如下输出:
我们可以观察到这是正确的输出,并得出结论:网页客户端工作正常。
改进深度学习后端
我们在这里训练的简单模型,几乎无法称之为一个接近完美的模型。有几种方法可以扩展这个模型,使其更好。例如,我们可以采取以下一些最基本的步骤来改进我们的深度学习模型:
-
增加训练轮次:我们只训练了模型 10 轮,这通常是一个非常小的值,对于任何深度学习模型来说都不够。增加训练轮次可以提高模型的准确性,但也可能导致过拟合,因此训练轮次需要通过实验来调整。
-
更多的训练样本:我们的网页客户端目前的功能仅限于显示预测值。但是,我们可以扩展它,让用户提供反馈,确认我们做出的预测是否正确。然后,我们可以将用户提供的输入图像添加到训练样本中,并用用户提供的标签来训练。不过,我们必须小心用户提交的垃圾输入图像和标签,因此此功能应仅提供给可信的用户或我们的网页应用的 Beta 测试人员。
-
创建更深的网络:我们可以增加网络中的隐藏层数量,以提高预测的准确性。但这种方法容易导致过拟合,因此必须小心实验。
总结
本章详细介绍了如何创建深度学习模型,并通过 API 使其在 Web 客户端或通过 cURL 使用。本章首先讨论了深度学习 Web 应用程序的结构、这些应用程序的各个组成部分以及它们如何相互作用。接着,简要介绍并探索了 MNIST 手写数字数据集。这为我们进入下一部分铺平了道路,在那里我们构建了一个深度学习模型并将其存储为文件以备将来使用。随后,这些文件被导入到服务器 API 脚本中,并在每次调用 API 时执行。最后,本章展示了一个非常基础的 API 客户端,并指导你如何通过命令行界面使用 cURL 来调用该 API。
在下一章,我们将讨论如何使用 TensorFlow.js 在浏览器窗口内执行深度学习。
第六章:开始使用 TensorFlow.js
到目前为止,我们已经轻松地了解了深度学习的奇妙世界,并对深度学习在使现代 Web 应用更加智能方面的潜力有了一个大致的了解。在第一章,揭开人工智能的神秘面纱与机器学习基础中,我们详细了解了 AI 突破前后的 Web 应用概况。在第三章,创建你的第一个深度学习 Web 应用中,我们使用一个简单的神经网络构建了一个基于图像分类的 Web 应用。
Web 应用无处不在,已经成为我们日常生活中不可或缺的一部分。在构建 Web 应用时,JavaScript 的使用几乎是无法忽视的。那么,如果我们只使用 JavaScript,而不依赖其他脚本语言,来构建一个智能的 Web 应用会怎样呢?在本章中,我们将展示如何使用一个名为 TensorFlow.js(TF.js)的 JavaScript 库,构建一个支持深度学习的 Web 应用——这一切都将在 Web 浏览器中完成。
在本章中,我们将涵盖以下主题:
-
TF.js 的基本原理及其功能
-
使用 TF.js 开发深度学习模型并进行推理
-
在浏览器中直接使用预训练模型
-
构建一个用于识别花卉种类的 Web 应用
-
TF.js 的优势与局限
技术要求
你可以访问本章使用的代码,地址为 github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter4
。
要完成本章的内容,你需要以下软件:
-
TF.js 0.15.1+
-
来自 NPM 仓库的
@tensorflow/tfjs-node
0.3.0+ 包
TF.js 的基本原理
在本节中,我们将简要回顾一些 TF.js 的基本概念。我们将从介绍 TensorFlow 开始,然后继续研究 TF.js 的不同组件。
什么是 TensorFlow?
在我们开始讨论 TF.js 之前,我们必须了解什么是 TensorFlow。TensorFlow 是一个由 Google 开发和维护的开源库。它基于一种称为张量的数据结构构建。张量是标量和向量的广义形式。TensorFlow 提供了许多高效的工具,适用于广泛的科学领域中的高性能数值计算。TensorFlow 还提供了一套非常灵活的工具,用于进行机器学习和深度学习的开发与研究。你可以访问 TensorFlow 的官方网站 www.tensorflow.org/
获取更多信息。
什么是 TF.js?
TF.js 是一个 JavaScript 库,提供了一个构建和部署机器学习模型的生态系统。它提供了以下功能:
-
使用 JavaScript 开发机器学习模型
-
使用预训练的机器学习模型
-
部署机器学习模型
TF.js 为你提供了机器学习项目所需的所有元素。它有专门的模块用于数据预处理、张量处理、模型构建、模型评估等,但所有这些都是用 JavaScript 完成的。在深入了解这些之前,让我们快速理解 TF.js 的必要性。
为什么选择 TF.js?
正如我们在前一章节中看到的那样,在线训练和托管一个模型、将其封装在一个 REST API 中,然后在任何前端使用 API 来展示我们的结果是非常简单和直观的。那么,为什么还会需要使用 TF.js 呢?
这个问题的简单答案是:如果浏览器中有 AI!想象一下一个游戏,它需要使用一个 AI 代理,这个代理通过学习人类玩家的玩法方式,在游戏进程中变得更强或更弱。如果在每一瞬间,游戏都不断向服务器发送请求,来传输数据到游戏和服务器之间,这就过于冗余了。更重要的是,这可能很容易导致拒绝服务(DoS)攻击。
因此,在浏览器中拥有一个可以自行生存并实时学习的 AI 是有意义的,尤其是当代理需要实时不断学习时。它也可以是两种方式的混合:
-
如果在代理渲染期间加载了一个预训练模型,并且从那里开始,代理会在服务器上间隔性地学习并更新模型。
-
如果多个版本的 AI 代理同时在几个系统上运行,并且它们从各自的系统互动中学习。而且,如果它们的集体学习被汇总到服务器上,代理将在间隔时从服务器获取更新。
因此,使用 TF.js 大大减少了对人类用户与服务器进行每一步通信时依赖页面的强度。
现在我们可以构建一个小项目,展示 TF.js 的强大功能。现在不用担心 TF.js 生态系统——我们会在过程中涵盖项目的所有元素。
TF.js 的基本概念
以下是我们将在项目中使用的 TF.js 组件:
-
张量
-
变量
-
操作
-
模型
-
层
让我们详细看看它们每个。
张量
与 TensorFlow 一样,TF.js 中的中央数据处理单元是张量。Goodfellow 等人(在他们的深度学习书中)做出了以下观察:
一般来说,按规则网格排列的数字数组,具有可变数量的轴,被称为张量。
简单来说,张量是一个容器,包含一维或多维数组。以下是一些你可能已经知道的张量示例:
-
标量(一个秩为零的张量)
-
向量(一个一维或秩为一的张量)
-
矩阵(一个二维或秩为二的张量)
我们可以根据给定的形状在 TF.js 中创建一个张量,如下所示:
const shape = [2, 3]; // 2 rows, 3 columns
const a = tf.tensor([4.0, 2.0, 5.0, 15.0, 19.0, 27.0], shape);
a
是一个已创建的张量,其内容可以使用以下命令打印出来:
a.print()
以下输出被打印:
Output: [[4 , 2 , 5 ],
[15, 19, 27]]
a
是一个矩阵(一个二阶张量)。TF.js 还提供了专门的函数,如 tf.scalar
、tf.tensor1d
、tf.tensor2d
、tf.tensor3d
和 tf.tensor4d
,用于创建具有特定形状的张量,而无需显式指定 shape
参数。这也提高了可读性。在 TF.js 中,张量是不可变的。
变量
与张量不同,变量在 TF.js 中是可变的。变量在神经网络训练过程中尤为重要,因为它们包含了大量中间数据存储和更新。以下是如何在 TF.js 中使用变量的示例:
const initialValues = tf.ones([5]);
const weights = tf.variable(initialValues); // initialize weights
weights.print(); // output: [1, 1, 1, 1, 1]
const updatedValues = tf.tensor1d([0, 1, 0, 1, 0]);
weights.assign(updatedValues); // update values of weights
weights.print(); // output: [0, 1, 0, 1, 0]
现在我们来看一下运算符。
运算符
运算符让你对数据进行数学运算。TF.js 提供了各种用于操作张量的运算符。由于张量本质上是不可变的,运算符不会改变张量中的数据——它们会返回新的张量作为结果。你可以对张量执行二元操作,如加法、乘法和减法。你甚至可以链式调用多个操作。以下示例展示了如何在 TF.js 中使用两个不同的运算符进行链式操作:
const e = tf.tensor2d([[1.0, 2.0], [3.0, 4.0]]);
const f = tf.tensor2d([[3.0, 4.0], [5.0, 6.0]]);
const sq_sum = tf.square(tf.add(e, f));
sq_sum.print();
我们首先创建了两个二维张量,并将它们分别赋值给e
和f
。接着,我们对它们进行了相加并求平方。
这会产生以下输出:
// Output: [[16 , 36],
// [64, 100]]
接下来,我们将讨论模型和层。
模型与层
在深度学习文献中,模型指的是神经网络本身,特别是神经网络架构。如在第二章《使用 Python 入门深度学习》中所讨论,神经网络由基本组件组成,如层、神经元以及层之间的连接。TF.js 提供了两个创建这些模型的函数——tf.model
和 tf.sequential
。tf.model
帮助你构建更复杂的架构,例如跳过某些层,而 tf.sequential
提供了一种创建线性层堆栈的方式,不涉及跳过、分支等操作。
TF.js 提供了多种专用层类型,用于不同任务的需求——tf.layers.dense
、tf.layers.dropout
、tf.layers.conv1d
、tf.layers.simpleRNN
、tf.layers.gru
和 tf.layers.lstm
。以下示例通过 tf.sequential
和 tf.layers.dense
展示了一个简单的神经网络模型:
const model = tf.sequential();
model.add(tf.layers.dense({units: 4, inputShape: [4], activation: 'relu'}));
model.add(tf.layers.dense({units: 1, activation: sigmoid}));
上述示例创建了一个简单的神经网络,其特点如下:
-
两个层(记住,在计算总层数时我们不考虑输入层)。该网络接受一个具有四个特征的输入(
inputShape
参数有助于指定这一点)。 -
第一层包含四个神经元(因此是
units: 4
)。第二层(输出层)只有一个神经元。 -
第一个层使用
relu
激活函数,输出层使用sigmoid
激活函数。
鼓励您访问 js.tensorflow.org/api/latest/index.html
了解 TF.js 的前述组件。
使用 TF.js 的案例研究
我们将遵循通常涉及到的机器学习项目中的所有步骤(我们在 第一章 中讨论过,揭秘人工智能和机器学习基础)。一个好的项目从一个清晰定义的问题陈述开始。因此,让我们快速浏览一下,并据此决定后续步骤。
我们 TF.js 小项目的问题陈述
在这里我们将要探讨的问题可能是你在机器学习初学时遇到的最著名的挑战之一——通过学习鸢尾花数据集中的特征来分类和预测鸢尾花的类型。训练和预测将在浏览器中执行。
我们已经为项目定义了问题陈述。接下来将进行数据准备步骤。数据已经可用,因此我们不需要自行收集。但是,在准备数据之前,了解数据本身的更多信息会很有帮助。
鸢尾花数据集
由统计学家兼生物学家罗纳德·费舍尔于 1936 年引入的鸢尾花数据集包含 150 行数据,大约有 3 种不同的鸢尾花品种。列名如下:
-
萼片长度(cm)
-
萼片宽度(cm)
-
花瓣长度(cm)
-
花瓣宽度(cm)
-
种类:
-
山鸢尾
-
变色鸢尾
-
维吉尼亚
-
您可以在 archive.ics.uci.edu/ml/datasets/Iris
获取原始数据集并了解更多。
您的第一个 TF.js 深度学习 Web 应用程序
在本节中,我们将使用 TF.js 开发一个 Web 应用程序。此应用程序将包括标准的全栈深度学习支持的 Web 项目步骤。我们将从准备数据开始,然后简要研究项目架构,然后按需构建所需的组件。
准备数据集
鸢尾花数据集原始形式是一个包含 150 行数据的 CSV 文件,以逗号分隔的格式,每个条目以新行分隔。
然而,为了更轻松地与 JavaScript 运行,我们将使用数据的 JSON 格式。JSON 格式的数据集可以从 gist.github.com/xprilion/33cc85952d317644c944274ee6071547
下载。
您可以使用任何语言中的简单函数,将 CSV 文件转换为 JSON 文件,按照以下约定更改列名:
-
萼片长度:
sepal_length
-
萼片宽度:
sepal_width
-
花瓣长度:
petal_length
-
花瓣宽度:
petal_width
-
种类:
species
在模型构建时,我们将使用这些属性名称的 JSON 格式。
项目架构
在本项目中,我们将使用 Node.js 来创建一个服务器。这样做是为了能够通过 Node.js 后端使用 TF.js,从而获得更快的计算性能。我们将创建一个非常基础的前端,用户可以通过它来发出命令,训练由 TF.js 构建的神经网络,并使用另一个按钮发出命令,根据用户提供的输入预测一朵鸢尾花的特征向量的类别。
以下图显示了项目的组成部分及其相互作用:
现在我们了解了架构,接下来让我们开始项目的开发。
启动项目
要开始进行项目开发,首先需要安装 Node.js 和Node 包管理器(NPM)的最新版本。虽然标准的做法是阅读 Node.js 官网提供的文档,我们建议使用Node 版本管理器(NVM)来安装 Node.js 和 NPM。
安装说明和文件可以在githu
b.com/creationix/nvm.
一旦安装了 Node.js 和 NPM,我们就可以开始进行项目的开发了:
-
创建一个名为
tfjs-iris
的文件夹。 -
打开终端,并使用以下命令初始化该项目的包管理器:
npm init -y
这应该会在项目目录中创建一个package.json
文件。前面的命令输出如下:
请注意,输出格式为 JSON 格式。main
键定义了在作为模块导入时程序的入口文件。在本项目中,main
的默认值设置为index.js
。不过,这个文件尚未创建。让我们来创建index.js
文件。
我们将使用 Node.js 的express
模块来创建我们的服务器。你可以在expressjs.com
上了解更多关于express
的信息。
- 要使用
express
,我们需要将这个模块添加到我们的项目中。为此,请使用以下代码:
npm install express --save
这将把express
模块的依赖添加到package.json
文件中,并将其安装到项目的node_modules
目录中。
- 在项目仓库的根目录下创建一个名为
index.js
的文件,并添加以下代码:
var express = require('express');
var app = express();
这将创建一个express
应用程序对象。接下来,我们将把 TF.js 添加到项目中。最简单的方法是通过 NPM 安装。完整的安装说明可以在js.tensorflow.org/setup/
找到。
- 使用以下命令在终端中安装
TF.js
模块:
npm install @tensorflow/tfjs --save
- 现在我们可以继续将该模块添加到我们的
index.js
文件中了:
const tf = require('@tensorflow/tfjs');
- 我们还需要 Express.js 中的
body-parser
模块来处理来自客户端的查询数据,这些数据将通过 AJAXPOST
请求发送。为此,我们使用以下命令:
npm install body-parser --save
- 我们现在创建一个
body-parser
对象,并使用以下代码将其绑定到应用程序:
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
在此阶段,package.json
应包含列出项目依赖项的以下代码片段:
请注意,前面的版本可能会发生变化。我们现在可以导入iris.json
文件,这是我们将要训练的模型数据:
const iris = require('./iris.json');
初始设置完成后,我们现在可以继续编写 TF.js 代码,在可用数据集上进行训练。
创建 TF.js 模型
首先,我们通过将存储在iris
变量中的数据读取到一个tensor2d
对象中:
- 在你的
index.js
文件中,添加以下代码:
const trainingData = tf.tensor2d(iris.map(item=> [
item.sepal_length, item.sepal_width, item.petal_length, item.petal_width
]),[144,4])
我们目前还没有任何测试数据;这些将由用户提供。
- 接下来,我们创建一个可能的三种花卉品种的独热编码:
const outputData = tf.tensor2d(iris.map(item => [
item.species === 'setosa' ? 1 : 0,
item.species === 'virginica' ? 1 : 0,
item.species === 'versicolor' ? 1 : 0
]), [144,3])
我们现在可以开始创建训练模型的代码。以下代码可能让你想起我们在上一章中为 MNIST 手写数字数据集创建模型时使用的代码。这仅仅是因为我们仍然在使用 TensorFlow 的概念,只不过是用不同的语言!
- 我们首先声明一个顺序的 TensorFlow 模型:
const model = tf.sequential();
- 接下来,让我们为模型添加一层神经元:
model.add(tf.layers.dense({
inputShape: 4,
activation: 'sigmoid',
units: 10
}));
inputShape
参数表示将添加到该层的输入的形状。units
参数设置该层中使用的神经元数量。我们使用的激活函数是sigmoid
函数。
- 现在让我们添加输出层:
model.add(tf.layers.dense({
inputShape: 10,
units: 3,
activation: 'softmax'
}));
在这里,我们将输出层中有 3 个神经元,且该层的输入为 10,这与前一层中的神经元数量相匹配。
除了输入层外,我们只有一个隐藏层和输出层。由于数据集较小且预测简单,因此在此应用中是可以接受的。请注意,我们在这里使用了softmax
激活函数,它会输出类别概率。
这在我们的案例中特别有用,因为问题是一个多类分类问题。
- 完成这一切后,我们现在可以准备编译我们的模型。为此,我们使用以下代码:
model.compile({
loss: "categoricalCrossentropy",
optimizer: tf.train.adam()
});
由于我们手头有一个分类问题,其中有多个可能的标签,因此我们使用categoricalCrossentropy
作为loss
函数。为了优化,我们使用adam
优化器。鼓励你尝试其他超参数值。
- 我们可以使用以下代码生成模型的摘要:
model.summary();
接下来,我们将训练我们的 TF.js 模型。
训练 TF.js 模型
我们现在将编写一个 async
函数。这样做的原因是为了让客户端 JavaScript 调用我们的函数时,不会因为等待结果而卡住。我们程序中需要一定时间才能完成的函数是 train_data()
函数。该函数负责模型的训练:
async function train_data(){
console.log("Training Started");
for(let i=0;i<50;i++){
let res = await model.fit(trainingData, outputData, {epochs: 50});
console.log(`Iteration ${i}: ${res.history.loss[0]}`);
}
console.log("Training Complete");
}
train_data()
函数可以异步运行。它还会在每个训练周期输出损失值到我们运行服务器的控制台中。现在,让我们创建一个 API 来调用 train_data()
函数。
首先,我们创建一个名为 doTrain
的 中间件,它将在训练 API 之前运行,并返回任何数据。
你可以在 expressjs.com/en/guide/using-middleware.html
阅读更多关于中间件的内容。
doTrain()
中间件在其参数中接受对 Node.js 服务器发出的请求、用于响应的变量,以及在执行完中间件中定义的代码块后用来转发程序执行的函数:
var doTrain = async function (req, res, next) {
await train_data();
next();
}
doTrain
中间件调用 train_data()
函数并等待其结果。train_data()
函数返回一个 Promise,以便执行可以继续而不会冻结。next()
函数在 train_data()
函数完成后立即运行,它只是将程序的执行传递给下一个链式调用的中间件函数,如下所示:
app.use(doTrain).post('/train', function(req, res) {
res.send("1");
});
我们现在将 '/train'
路由绑定到 express
应用中,然后将 doTrain
中间件链接到它。现在,每当调用 '/train'
API 时,中间件会首先运行,然后执行会传递到 API 的主代码块。这个代码块简单地返回一个任意值,表示训练已完成。
使用 TF.js 模型进行预测
训练完成后,我们还需要创建一个 API 来调用预测函数并返回预测结果。我们将 API 绑定到 '/predict'
路由,并使用 POST
方法发起对该 API 的请求,如下所示:
app.post('/predict', function(req, res) {
var test = tf.tensor2d([parseFloat(req.body.sepLen), parseFloat(req.body.sepWid), parseFloat(req.body.petLen), parseFloat(req.body.petWid)], [1,4]);
var out = model.predict(test);
var maxIndex = 0;
for (let i=1;i<out.size; i++){
if (out.buffer().get(0, i) > out.buffer().get(0, maxIndex)){
maxIndex = i;
}
}
ans = "Undetermined";
switch(maxIndex) {
case 0:
ans = "Setosa";
break;
case 1:
ans = "Virginica";
break;
case 2:
ans = "Versicolor";
break;
}
console.log(ans);
res.send(ans);
});
理解预测 API 的代码非常简单。让我们分部分讨论:
app.post('/predict', function(req, res) {
这一行将 '/predict'
路由绑定到 POST
请求方法,并打开处理请求代码块,处理所有对该路由的请求:
var test = tf.tensor2d([parseFloat(req.body.sepLen), parseFloat(req.body.sepWid), parseFloat(req.body.petLen), parseFloat(req.body.petWid)], [1,4]);
var output = model.predict(test);
这些行创建了一个 TF.js tensor2d
对象,数据是从客户端接收到的。接着,它在模型上运行 predict
方法,并将结果存储在 output 变量中:
var maxIndex = 0;
for (let i=1;i<out.size; i++){
if (out.buffer().get(0, i) > out.buffer().get(0, maxIndex)){
maxIndex = i;
}
}
这个代码块仅仅是找出在 tensor2d
变量 output 中对应最大元素的索引。记住,在 softmax
激活输出中,最高值对应着预测的索引。
在确定输出的最大索引之后,我们使用一个简单的 switch-case 语句来决定从 API 发送到客户端的输出内容。请求数据也会记录在服务器的控制台中。最后,我们使用以下代码将 Node.js 应用绑定到监听 3000
端口:
app.listen(3000);
接下来,我们将创建一个简单的客户端。
创建一个简单的客户端
为了处理应用程序中的 '/'
路由,我们向 index.js
添加以下代码行,它仅渲染一个静态文件 index.html
,该文件位于 public 文件夹中:
app.use(express.static('./public')).get('/', function (req, res) {
res.sendFile('./index.html');
});
现在,让我们通过以下步骤创建静态的 index.html
文件:
- 首先,创建一个文件夹,
public
,并在其中创建index.html
文件。将以下代码添加到index.html
文件中:
<html>
<head>
<title>TF.js Example - Iris Flower Classficiation</title>
</head>
<body>
<h1> TF.js Example - Iris Flower Classification </h1>
<hr>
<p>
First, train the model. Then, use the text boxes to try any dummy data.
</p>
<button id="train-btn">Train</button>
<hr><br>
<label for="sepLen">Sepal Length: </label>
<input type="number" id="sepLen" value="1" /><br>
<label for="sepWid">Sepal Width: </label>
<input type="number" id="sepWid" value="1" /><br>
<label for="petLen">Petal Length: </label>
<input type="number" id="petLen" value="1" /><br>
<label for="petWid">Petal Width: </label>
<input type="number" id="petWid" value="1" /><br>
<br>
<button id="send-btn" disabled="="true">Predict!</button>
<hr>
<h3> Result </h3>
<h4 id="res"></h4>
<script src="img/jquery.min.js"></script>
- 在为客户端设置了一个简单的 UI,调用我们使用 TF.js 创建的 API 后,我们准备好定义功能并从客户端部署它们。请注意,
"/train"
和"/predict"
两个 API 都将通过POST
请求进行调用:
<script>
$('#train-btn').click(function(){
$('#train-btn').prop('disabled', true);
$('#train-btn').empty().append("Training...");
$.ajax({
type: 'POST',
url: "/train",
success: function(result) {
console.log(result);
$('#send-btn').prop('disabled', false);
$('#train-btn').empty().append("Trained!");
}
});
});
$('#send-btn').click(function(){
var sepLen = $('#sepLen').val();
var sepWid = $('#sepWid').val();
var petLen = $('#petLen').val();
var petWid = $('#petWid').val();
$.ajax({
type: 'POST',
url: "/predict",
data: {sepLen: sepLen, sepWid: sepWid, petLen: petLen, petWid: petWid},
success: function(result) {
console.log(result);
$('#res').empty().append(result);
}
});
});
</script>
</body>
</html>
现在让我们运行 TF.js Web 应用。
运行 TF.js Web 应用
完成所有应用编码后,我们现在准备运行应用程序。首先,打开终端,并将包含 package.json
文件的 tfjs-iris
文件夹设置为工作目录。
运行以下代码行以启动 Node.js 服务器:
node index.js
该命令产生的输出类似于以下截图:
现在,随着此输出,服务器在 3000
端口启动,我们可以在浏览器中查看相同内容。打开浏览器并在地址栏输入 http://localhost:3000/
来显示以下输出:
首先,你必须点击训练按钮以调用'/train'
API,这将开始训练,并且按钮会变为禁用状态。一旦预测按钮启用,训练完成,用户就可以向服务器发送虚拟数据进行预测。假设我们选择数据集中的第 50 行数据,并将其发送给服务器,期望的输出是Setosa
。
以下截图展示了我们项目最终版本的一小部分:
我们看到为提供的输入生成了正确的输出。
TF.js 的优点和限制
现在让我们总结一些 TF.js 相比 TensorFlow 的优势,除了我们在本章中已经讨论的那些:
-
自动 GPU 支持:使用 TF.js 时,你无需单独安装 CUDA 或 GPU 驱动程序即可利用系统中的 GPU。这是因为浏览器本身实现了 GPU 支持。
-
集成:将 TF.js 集成到使用 Node.js 的 Web 开发项目中相当简单,然后将预训练模型导入到项目并在浏览器中运行它们。
然而,它也有几个缺点,无论何时开发用于生产的时候都需要牢记。其中一些如下:
-
速度:TF.js 适合小数据集。在大规模数据集上,计算速度显著下降,几乎慢了 10 倍。
-
缺少 TensorBoard:这个伟大的工具,用于可视化 TensorFlow 模型,在 JavaScript 版的框架 TF.js 中缺失,因为 TF.js 仅仅是一个 API。
-
API 的支持不完整:并非所有 TensorFlow 的 API 在 TF.js 中都可用,因此在开发 TF.js 时,您可能需要重新考虑代码逻辑或创建自己的函数来使用某些功能。
总结
在这一章中,我们学习了使用 TF.js 创建模型是多么容易。你不仅可以使用整个 JavaScript 生态系统,而且还可以使用 TF.js 中的所有预训练 TensorFlow 模型。我们使用鸢尾花数据集开发了一个简单的 Web 应用程序,在此过程中,我们了解了 TF.js 提供的几个组件。到目前为止,我们已经构建了两个简单的端到端基于深度学习的 Web 应用程序。
我们的进展确实显而易见。在接下来的章节中,我们将构建我们自己的深度学习 API,并使用它们创建智能 Web 应用程序。但在此之前,让我们在下一章中对 API 的整体概念有所了解。
第七章:使用不同深度学习 API 进行网页开发入门
本节讲解了 API 在软件开发中的一般使用,并展示了如何利用不同的最先进深度学习 API 来构建智能网页应用。我们将涵盖包括自然语言处理(NLP)和计算机视觉在内的领域。
本节包括以下章节:
-
第五章,通过 API 进行深度学习
-
第六章,在 Google Cloud 平台上使用 Python 进行深度学习
-
第七章,在 AWS 上使用 Python 进行深度学习:物体检测与家居自动化
-
第八章,在 Microsoft Azure 上使用 Python 进行深度学习
第八章:通过 API 进行深度学习
到目前为止,我们已经熟悉了深度学习项目中遵循的基本流程。在之前的章节中,我们使用 Keras 和 TensorFlow.js 库完成了两个基本的端到端项目。我们已经熟悉了 Python 库,如 NumPy、pandas 和 Keras,我们也看到如何使用 JavaScript 开发深度学习模型。我们还使用 Flask 框架将深度学习模型转换成 API。在 第四章,开始使用 TensorFlow.js 中,我们使用了第三方 应用程序编程接口(API)来创建 web 应用程序。
在本章中,我们将详细学习 API 的整个概念。从 API 的一个非正式定义开始,我们将探讨所有与深度学习相关的 API。我们首先会看看一些广为人知的深度学习 API,然后再了解一些鲜为人知的深度学习 API。我们还将学习如何选择深度学习 API 提供商。
在本章中,我们将涵盖以下主题:
-
什么是 API?
-
API 与库有什么不同?
-
一些广为人知的深度学习 API
-
一些鲜为人知的深度学习 API
-
选择深度学习 API 提供商
什么是 API?
让我们首先考虑一个问题场景。
假设你正在开发一个需要集成图像识别模块的 web 应用程序。但是你对计算机视觉和深度学习并不熟悉,你有一个非常严格的项目截止日期。你不能花时间去学习深度学习然后再完成图像识别模块的开发。你现在应该怎么办?你的项目能按时完成吗?
当然不能!然而,借助 API 的力量,你将能够轻松地将图像识别模块集成到你的 web 应用程序中。现在,让我们更详细地讨论一下 API 的概念。
API 是一组函数(虽然从技术上讲,一个 API 也可以只包含一个函数),可以集成到应用程序中执行特定任务。通常,作为开发者,我们会希望将我们喜欢的网站上的某些实用工具集成到我们自己的应用程序中。例如,Twitter 提供了一个 API,用于检索与特定关键词匹配的推文。我们可以使用这个 API 来收集数据,分析数据,最终得出一些有趣的见解。
像 Facebook、Google、Stack Overflow 和 LinkedIn 等公司提供了用于特定任务的 API,对于开发者来说,了解这些 API 非常值得。API 几乎可以类比为网站。当我们点击网站中的某个内容时,我们会被重定向到另一个页面/部分。在大多数情况下,我们会看到一个网页作为输出。但是 API 通常不会生成好看的网页作为输出。API 的设计目的是在代码中使用,它们的输出通常是某种流行的数据交换格式,如 JSON 或 XML。然后,输出会根据应用程序的需求进行处理。API 让您通过提供一套实用工具或一个生态系统来完成您想做的任务,而无需担心细节问题。
如今,您可以测试一个 API,而无需编写一行代码。例如,您可以使用像 Postman 这样的 API 客户端,测试您喜欢的开放 API,而无需编写任何代码来实现这一点。
API 更神奇的一点在于,您可以用 Java 编写代码,并使用一个用 Python 开发的 API。这在团队协作中尤为有用,尤其是当团队成员对使用的编程语言非常讲究时。一个团队成员可能非常熟悉 Java,而另一个成员则可能是 Python 专家。因此,在这些情况下,API 的概念非常实用。
我们接下来将讨论一些由 Google AI、Facebook AI Research 等公司提供的深度学习 API。我们将在接下来的章节中看到如何使用这些 API 来开发智能 web 应用。
使用 API 的重要性
除了在需要快速生产或最小工作产品演示时,节省您大量的精力来创建和部署自己的深度学习模型外,API 还可以带来以下几种好处:
-
标准、稳定的模型:
- 深度学习的 API 通常是由一整个开发者团队共同创建的,这些开发者使用行业标准的技术和研究工具,而这些工具可能并不是所有开发者都能接触到的。此外,通过商业 API 部署的模型通常非常稳定,提供了最先进的功能,包括可扩展性、定制性和准确性。因此,如果您在准确性方面遇到问题,这是深度学习模型中常见的情况,选择 API 是一个不错的选择。
-
高性能模型:
- 商业深度学习 API 通常运行在非常强大的服务器上,并经过高度优化,能够非常快速地执行任务。因此,如果您希望加速深度学习生态系统的学习,这类 API 非常方便。
-
开发者的共同平台:
- 虽然从零开始编写代码非常简单,但当最初编写代码的人离开并且没有提供适当的文档,而新的人必须接手时,事情就变得很难了。商业 API 定义了一套标准操作,使用这些 API 构建的应用程序容易维护,因为 API 提供商通常会提供详尽的文档,意味着开发者可以事先了解 API。
-
定期和无缝更新:
- 对于一个处于初创阶段的公司来说,一旦他们开发出了第一个版本的深度学习模型,尤其是当他们的整个商业模式并不完全以人工智能为中心时,承担开发时间以改进深度学习模型往往是昂贵的。任何类似的应用场景都会从 API 的使用中大大受益,因为 API 是由那些定期推送更新和新功能的人来维护的。
考虑到这一切,使用 API 能够提供最新的技术、高性能和不断发展的模型,这些模型可以一次集成到应用程序中,并且多年使用而不必再考虑这些 API。
现在,你可能会问 API 和库有什么区别。让我们在下一节中解答这个问题。
API 和库有什么不同?
如今,库和API这两个术语经常互换使用。它们之间有许多相似之处,但在许多方面也有所不同。就像 API 一样,库也提供了一组函数和类,可以根据需要使用。以下是一些帮助你区分库和 API 的要点:
-
库通常是特定于编程语言的。例如,如果你使用 PHP 编程环境,就无法使用 SciPy Python 库。然而,你可以开发一个使用 SciPy 的 API,然后通过 PHP 代码调用这个 API。
-
开发者无法直接访问 API。API 的使用方式与库有所不同。许多 API 在开发者实际使用之前要求进行某种身份验证。而当使用库时,这种情况并不常见。你可以轻松地重载和重写库的函数或类,并根据自己的需要使用它们。
-
库和 API 可以一起使用。许多库内部使用不同的 API,反之亦然。
这些应该能让你对库和 API 之间的基本区别有一定的了解。不过,如果你仍然觉得难以区分,不用担心:我们会看很多例子,到你看完这些例子时,你一定能够区分 API 和库。
现在,我们将介绍一些广泛用于开发深度学习应用程序的 API,其中一些非常知名,另一些则不那么流行。
一些广为人知的深度学习 API
在本节中,我们将看看一些最广泛使用的 API,这些 API 部署在多种深度学习任务中,如图像识别、从图像中检测情感、情感分类、语音转文本转换等。为了限制本节的讨论,我们将深度学习任务分为两个大类:
-
计算机视觉和图像处理
-
自然语言处理
然后,我们将列出与这些分类相关的一些常见任务,并讨论可以用来完成这些任务的 API。
现在我们快速列出一些常见的深度学习任务,并将其归类:
-
计算机视觉和图像处理:
-
图像搜索:就像 Google 搜索一样,图像搜索引擎允许我们搜索与某一特定图像相似的图像。
-
图像检测:这是检测图像内容的功能,也被称为标签检测。
-
对象定位:给定一张包含多个不同对象的图像,这涉及检测图像中特定的对象。
-
内容审核:给定一张图像,这涉及检测图像中的不当内容。
-
图像属性提取:给定一张图像,这涉及提取图像的不同特征。
-
-
自然语言处理:
-
词性标注:给定一段文本,这涉及提取该文本中包含的词性。
-
主题摘要:给定一段文本,这涉及确定该文本所涉及的主题。
-
情感分类:给定一段文本,这涉及预测该文本所传达的情感。
-
命名实体识别:这涉及自动识别给定句子中的不同实体。
-
语音转文本转换:这涉及从语音中提取包含的文本。
-
这里列出的所有任务在我们的日常生活中都极为有用,令人兴奋的是,我们可以利用即将讨论的 API 开发出能为我们完成这些任务的应用程序。
还有其他用于大规模推理的深度学习 API,但暂时我们可以忽略它们,集中讨论两大最受深度学习影响的领域。
以下表格汇总了一些在行业中最广泛使用的深度学习 API:
提供者 | API | 分类 |
---|---|---|
Vision API | 计算机视觉和图像处理 | |
视频智能 API | ||
自然语言 API | 自然语言处理 | |
语音转文本 API | ||
语音合成 API | ||
翻译 API | ||
Dialogflow API | ||
DensePose | 计算机视觉和图像处理 | |
Detectron | ||
Amazon | Amazon Rekognition | 计算机视觉和图像处理 |
Amazon Comprehend | 自然语言处理 | |
Amazon Textract | ||
Amazon Polly | ||
Amazon Translate | ||
Amazon Transcribe | ||
Microsoft | 计算机视觉 | 计算机视觉和图像处理 |
视频索引器 | ||
面部识别 | ||
内容审核员 | ||
文本分析 | 自然语言处理 | |
必应拼写检查 | ||
翻译文本 | ||
语言理解 |
上表中显示的是使用过的、经过良好测试并且具有可扩展性的最流行的深度学习 API。然而,还有一些其他名字虽然还未像这些一样流行,但也正在成长。在下一节中,我们将看看它们。
一些较少为人知的深度学习 API
下表给出了一些较少为人知的 API 的详细信息:
提供商 | API | 类别 |
---|---|---|
IBM Watson | Watson 虚拟识别 | 计算机视觉与图像处理 |
Watson 文本转语音 | 自然语言处理 | |
Watson 自然语言分类器 | ||
Watson 对话 | ||
Watson 自然语言理解 | ||
AT&T | AT&T 语音 | 自然语言处理 |
Wit.ai | 语音 | 自然语言处理 |
消息 | ||
实体 |
现在,在这片广阔(嗯,几乎)API 的海洋中,如何为特定任务选择一个特定的提供商呢?这可能会很棘手,需要深入讨论。在本节中,我们将讨论一些策略,帮助我们有效地做出这些决定。
选择深度学习 API 提供商
在长长的深度学习 API 提供商列表中,选择所需的 API 可能是一个令人生畏的任务。然而,你可以遵循一些简单的规则,帮助你找到最适合你需求的 API,我们将在这里详细讨论其中的一些规则:
-
平台:
-
听起来很简单,但在选择 API 提供商时,这可能是最重要的因素。大多数情况下,如果你正在开发一个基于 Google 技术的产品,例如,你可能会倾向于使用 Google 提供的深度学习 API,因为它们与开发应用程序的接口能够无缝集成。
-
更多时候,开发环境还提供使用其深度学习 API 的模板化解决方案,设置起来非常简单。有时,提供商还会为使用其 API 开发新产品提供额外的奖励。
-
-
性能:
- 在可以访问多个提供商的 API 来执行单一任务时,你可以比较它们的性能后进行选择。在这种情况下,选择比较和评判不同 API 时使用的度量标准由你决定。
-
成本:
- 不同的提供商使用不同的计费方式,这在决定使用哪个提供商时起着至关重要的作用。某个提供商可能对免费 API 调用次数有一个相对宽松的限制,因此可能是一个很有吸引力的选择。开发者和学生通常选择那些在成本方面最具优势的提供商进行试验。
除了这三个因素,还有一些其他不可忽视的因素,比如公司要求使用某个特定的 API 或者你自己对某个 API 提供商的偏好。然而,除非是大规模应用,否则所选择的提供商通常没有太大影响,因为它们在中小规模的使用中提供的性能差异并不明显。
总结
在本章中,我们详细讨论了术语 API。在第三章,创建你的第一个深度学习 web 应用程序中,我们看到了如何使用 Flask 在 Python 中编写 API,并且了解了如何在 web 应用程序中使用该 API。现在我们知道了 API 与语言库的区别,以及利用 API 的重要性。我们也熟悉了一些顶级组织提供的各种深度学习 API。
随着我们继续深入接下来的章节,我们将看到如何利用这些 API 来构建强大且智能的 web 应用程序。我们将在下一章开始使用 Google Cloud Platform 提供的深度学习 API。
第九章:在 Google Cloud Platform 上使用 Python 进行深度学习
在上一章,我们看到了一些由各个组织提供的深度学习 API。我们还看到它们的应用范围大致可以分为两类——第一类是计算机视觉和图像处理,第二类是自然语言处理。我们将在本章继续探索深度学习 API。本章将介绍 Google Cloud Platform (GCP) 及其在深度学习领域提供的三个 API。
本章我们将涵盖以下主题:
-
设置你的 GCP 账户
-
在 GCP 上创建你的第一个项目
-
在 Python 中使用 Dialogflow API
-
在 Python 中使用 Cloud Vision API
-
在 Python 中使用 Cloud Translation API
技术要求
你可以从 github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter6
获取本章的代码。
要运行本章的代码,你的系统需要安装 Python 3.6 及以上版本。
本章过程中还会介绍其他必需的安装。
设置你的 GCP 账户
在使用 GCP 提供的 API 之前,你必须先设置 GCP 账户。假设你已经有一个 Google 账户——首先,前往 cloud.google.com/
。如果你是第一次注册 GCP,GCP 会提供 $300 的信用额度(有效期为 12 个月);这笔信用额度足够支持许多良好的项目并让你试用 GCP 的服务。完成后,我们可以按照以下步骤操作:
- 在 GCP 首页的右上角,你应该能找到一个“免费试用”按钮。点击它:
- 如果你尚未登录 Google 账户,系统会要求你登录。根据提示选择你的国家,并确保勾选服务条款框。之后,点击同意并继续。你会看到如下截图页面:
- 然后,系统会要求你输入所选择的支付方式的详细信息。即使你有免费的信用额度,为了使用 GCP 的服务,你也需要设置一个有效的计费账户。但不用担心,除非你允许 GCP 否则不会从你的计费账户中扣费。在免费试用期间,你在 GCP 上使用的所有计费服务将仅从你的免费信用额度中扣除。一旦免费信用额度用尽,GCP 会向你发送提醒。
完成计费手续后,你应该会进入 GCP 的控制台页面,界面如下所示:
这是你的 GCP 仪表板,它会给你一个 GCP 使用情况的总体概览。GCP 还允许你自定义在控制台上显示的标签。
现在你应该已经完成了 GCP 账户的设置。为了能够使用 GCP 中的工具,你需要创建一个带有有效计费账户的 GCP 项目。在接下来的章节中,你将看到如何操作。
在 GCP 上创建你的第一个项目
项目帮助你系统地组织所有的 GCP 资源。创建 GCP 项目只需几个点击步骤:
- 在登录你的 Google 账户后,使用
console.cloud.google.com
打开 GCP 控制台。在左上角,你应该看到 Google Cloud Platform,并且就在旁边,你可以看到一个下拉列表,如下所示:
- 如果你在注册 GCP 时或之前创建了任何项目,那么你的项目将出现在标记区域(我在 GCP 上创建了 fast-ai-exploration 和 gcp-api 两个项目)。现在,点击下拉箭头,应该会弹出一个窗口:
- 点击 "NEW PROJECT" 继续。你将进入一个页面,如下图所示,页面会要求你指定项目名称。GCP 会自动为你创建项目的 ID,但你也可以根据自己的需求编辑该 ID:
- 在你指定了项目的初步细节后,只需点击 "CREATE" 项目即可创建。创建完成后,项目应该会出现在项目列表中。你可以随时通过 GCP 控制台页面提供的下拉菜单访问该列表。你可以在下图中看到这个界面:
如果你想了解更多关于 GCP 项目的信息,可以查看官方文档 cloud.google.com/storage/docs/projects
。GCP 提供了一整套广泛的实用工具,你可以在 cloud.google.com/products/
找到它们。我们鼓励你查看并根据个人兴趣进行探索。
GCP 为我们提供了广泛的 API,可以用于多种任务,包括深度学习。在接下来的几个章节中,我们将看到如何使用 Python 代码调用一些最常用的深度学习 API。我们将从 Dialogflow 开始。
在 Python 中使用 Dialogflow API
在我们开始学习如何在 Python 中使用 Dialogflow API 之前,让我们先了解一下 Dialogflow 是什么。
Dialogflow(前身为api.ai)提供了一套用于构建自然且丰富的对话界面的工具,如语音助手和聊天机器人。它由深度学习和自然语言处理技术驱动,并被大量公司使用。它与网站、移动应用程序以及许多流行的平台(如 Facebook Messenger、Amazon Alexa 等)无缝集成。Dialogflow 为我们提供了构建对话式用户界面的三个主要组件:
-
可以轻松应用于任何对话式用户界面的最佳实践和流程
-
添加任何构建对话式用户界面所需的自定义逻辑的功能
-
用于训练代理的功能,以便优化界面的整体体验
现在,我们将看到如何使用 Dialogflow 创建一个简单的 Python 应用程序。您可以访问 dialogflow.com
了解更多关于 Dialogflow 的信息。
我们将从创建一个 Dialogflow 帐户开始。
创建一个 Dialogflow 帐户
创建一个 Dialogflow 帐户非常简单。整个过程包含以下步骤:
- 访问
console.dialogflow.com/api-client/#/login
,您将看到以下界面:
-
点击“使用 Google 登录”按钮后,系统将要求您选择一个要与 Dialogflow 一起使用的 Google 帐户。
-
选择帐户后,可能会要求您允许 Dialogflow 的帐户权限,并接受 Dialogflow 的条款和条件。
创建一个新代理
创建帐户后,您将看到一个仪表板,仪表板将显示您的活动 Dialogflow 项目,或者提示您创建一个新的代理并进行显示——但是,什么是代理呢?
代理——在 Dialogflow 的术语中——是一个软件,负责接收用户输入,这些输入可能是文本、音频、图片或视频格式。然后,它尝试确定意图,即与输入对应的预定义的适当操作。匹配的意图可能会执行一个动作,或者仅仅返回一个超文本响应,回应用户的查询。最后,代理将结果返回给用户。
要创建一个新代理,请在 Dialogflow 控制台的左侧导航菜单中点击“创建代理”。
您将看到如下所示的界面:
我们将代理命名为 DemoBot,并将默认语言设置为英语。此外,我们还需要为该代理选择一个 Google 项目。
Google 项目——或简而言之,项目——是你在学习 GCP 时遇到的一个术语。项目包含了为任何使用这些资源的软件项目分配的所有资源,这些资源由 GCP 上的一个单一账单账户进行融资。在没有为资源定义项目的情况下,无法分配任何资源。此外,在没有添加有效账单选项的情况下,无法创建项目。
现在你将能够看到如下截图中显示的界面,其中为你的代理提供了一些默认意图:
在左侧,你可以看到导航菜单,提供了所有可以组合在一起,以便软件提供更具人性化交互的各种模块。在右侧面板中,你可以随时测试你的代理,输入任何你提供的内容。这在响应开发和测试输入匹配意图时会非常有用。
创建一个新意图
要为我们的代理创建一个新意图,按照以下步骤操作:
-
点击中间区域右上角的创建意图按钮。
-
你需要为这个意图提供一个名称——我们称之为
Dummy Intent
。 -
然后我们需要提供一些训练短语,这些短语会触发此意图。假设我们提供了三个训练短语,如下所示:
现在,我们可以预期,当系统遇到训练中提到的短语(或类似短语)时,这个意图会被调用。
- 现在我们可以添加一些响应,当这个意图被调用时,代理将作出响应,如下所示:
- 在中间区域的右上角,点击保存按钮以保存新意图,系统会通知你代理训练已开始。
对于一个小型代理,训练在几秒钟内完成,系统会显示“代理训练完成”的通知。
我们现在准备测试我们的代理是否能够执行这个意图。
测试你的代理
在你 Dialogflow 控制台的右侧区域,你可以测试你的代理。在顶部文本框中输入你的查询。在我们的代理中,调用Dummy Intent
时,我们输入Talk to the dummy
。
如果意图正确匹配,你将能够看到来自Dummy Intent
的响应,如下所示:
在前面的截图中,你会看到用户的输入是Talk to the dummy
,生成的响应是我们为Dummy Intent
定义的两个响应之一。你可以看到,匹配到输入的意图是Dummy Intent
。
现在我们来看看如何使用 Python 调用代理。
安装 Dialogflow Python SDK
在本节中,我们将演示如何使用 Dialogflow Python API V2 与您的 Dialogflow 代理配合使用,将互动性带入您使用 Python 构建的应用程序。首先,让我们通过以下图表理解 DialogFlow 生态系统的多个组件如何交互:
用户创建输入内容,并通过集成 API、网站或应用将其发送给代理。代理将用户输入与可用的意图进行匹配,并生成 查询 的响应。响应通过 Webhook 发送回用户界面,并展示给用户。
集成 API 很可能会包含 Dialogflow 以外的服务。您可以创建一个应用程序,将相同的用户查询传播到多个代理,并汇总它们的响应。
或者,开发者可以引入中间件处理程序或集成,这些程序会对用户查询和代理响应进行预处理或后处理:
- 要安装 Dialogflow Python SDK,我们在终端使用以下命令:
pip install dialogflow
强烈建议在使用之前的命令之前,使用 virtualenv
创建一个虚拟环境,以便拥有干净且不间断的依赖关系。要了解有关 virtualenv
的更多信息,请参考 virtualenv.pypa.io/en/latest/
。
- 安装完成后,您可以使用以下导入代码将 Dialogflow API 导入到您的项目中:
import dialogflow
现在我们将创建一个 GCP 服务账户来认证我们的 Python 脚本,以便使用我们创建的 Dialogflow 代理。
创建 GCP 服务账户
GCP 服务账户管理用于访问 GCP 资源的权限。我们创建的 Dialogflow 代理是一个 GCP 资源,因此要通过 Python API 使用它,我们需要一个服务账户:
-
在 GCP 控制台中,从左侧导航菜单中,前往 APIs | Services | Credentials。
-
点击“创建凭证”按钮,获取以下选项:
-
点击“服务账户密钥”。在接下来的页面中,选择 Dialogflow 集成作为服务账户,JSON 作为密钥类型。点击“创建”后,一个 JSON 文件将下载到您的计算机。
-
记下此 JSON 文件的地址,例如
/home/user/Downloads/service-account-file.json
。文件名可能会有所不同,因为它是由 GCP 控制台在您下载文件到计算机时提供的。 -
打开此文件以获取项目 ID。
-
现在,在终端使用以下命令——根据您系统上的实际情况进行适当替换——将凭证导出到环境变量中:
-
- 在 Linux(终端)中:
export GOOGLE_APPLICATION_CREDENTIALS="<your_service_account_file_location>"
export DIALOGFLOW_PROJECT_ID="<your_project_id>"
-
- 在 Windows(命令提示符)中:
set GOOGLE_APPLICATION_CREDENTIALS=<your_service_account_file_location>
set DIALOGFLOW_PROJECT_ID=<your_project_id>
完成此步骤后,我们现在准备编写调用 Dialogflow 代理的 Python 脚本。
请注意,上述命令仅设置当前会话的变量。每次重新启动会话时,您需要运行这些命令。
使用 Python API 调用 Dialogflow 代理
在这个示例中,我们将创建一个简单的基于 Python 的 API,调用我们在 Dialogflow 控制台中创建的代理来调用 Dummy Intent
,如下面所示:
- 首先,我们必须将 Dialogflow 模块导入项目。为此,请使用以下代码:
import dialogflow
- 要将项目 ID 引入脚本中,我们可以从运行时环境变量中获取它。为此,请使用以下代码:
import os
project_id = os.getenv("DIALOGFLOW_PROJECT_ID")
- 我们还将声明一个唯一的会话 ID,以存储与用户进行的任何单个会话的对话记录:
session_id="any_random_unique_string"
- 现在我们将创建一个方便的函数,允许我们反复执行一组调用 Dialogflow 代理所需的预处理语句:
def detect_intent(project_id, session_id, text, language_code):
session_client = dialogflow.SessionsClient()
session = session_client.session_path(project_id, session_id)
text_input = dialogflow.types.TextInput(text=text, language_code=language_code)
query_input = dialogflow.types.QueryInput(text=text_input)
response = session_client.detect_intent(session=session, query_input=query_input)
return response.query_result.fulfillment_text
在上述代码中,我们首先初始化一个 SessionsClient
对象。会话记录用户与 Dialogflow 代理在一次不间断对话中的完整互动。接下来,我们必须设置会话路径,即将项目映射到一个唯一的会话 ID。
上面函数定义的接下来的两行代码用于创建一个包含 Dialogflow TextInput
对象的 QueryInput
对象。query_input
变量保存用户输入的消息,用于 Dialogflow 代理。
下一行代码调用了 SessionsClient
对象的 detect_intent()
方法。会话 ID-项目 ID 映射及输入作为参数传递给该方法。Dialogflow 代理的响应存储在响应变量中。该函数返回满足文本响应。
- 现在让我们使用这个方法。首先,声明一个消息并传递给 Dialogflow 代理。回想一下我们为
Dummy Intent
提供的训练短语。我们将传递一个类似于训练短语的消息:
message = "Can I talk to the dummy?"
fulfillment_text = detect_intent(project_id, session_id, message, 'en')
print(fulfillment_text)
我们将获得一个输出,它是我们为 Dummy Intent
定义的两个响应之一。
- 在
detect_intent()
方法中生成响应变量,可以通过在detect_intent()
函数中添加以下代码行来实现:
def detect_intent(project_id, session_id, text, language_code):
...
response = session_client.detect_intent(session=session, query_input=query_input)
print(response) ### <--- ADD THIS LINE
return response.query_result.fulfillment_text
您将获得以下 JSON:
response_id: "d1a7b2bf-0000-0000-0000-81161394cc24"
query_result {
query_text: "talk to the dummy?"
parameters {
}
all_required_params_present: true
fulfillment_text: "Congratulations, dummy intent user!"
fulfillment_messages {
text {
text: "Congratulations, dummy intent user!"
}
}
intent {
name: "projects/gcp-api-232806/agent/intents/35e15aa5-0000-0000-0000-672d46bcefa7"
display_name: "Dummy Intent"
}
intent_detection_confidence: 0.8199999928474426
language_code: "en"
}
您将观察到,匹配到的意图名称是 Dummy Intent
,而我们在这次调用代理中获得的输出是 恭喜,虚拟意图用户!
。
使用 Python 调用 Dialogflow API 的方式有很多种,包括—但不限于—音视频输入和基于传感器的输入。Dialogflow 代理可以与主要平台集成,例如 Google Assistant、Facebook Messenger、Slack、Telegram、WhatsApp 等,具体如下:
Dialogflow 生态系统正在快速推出新功能,并且越来越朝着提供能够同时执行多项任务的基于 AI 的完整聊天机器人方向发展。
在接下来的章节中,我们将探索另一个可以用于预测图像和视频内容的 GCP API。
在 Python 中使用 Cloud Vision API
计算机视觉是使计算机理解图像并从中提取信息的领域。常见的计算机视觉任务包括图像分类、图像检测、图像分割等。如前几章所述,计算机视觉领域受到深度学习在实现人类水平(有时甚至更好)性能方面的有效性影响很大。
Cloud Vision API 提供了许多工具来执行计算机视觉任务。Cloud Vision 允许我们使用预训练模型,也可以构建我们自己的定制化生产就绪模型,以满足我们的需求(例如 AutoML Vision Beta)。现在让我们简要看看 Cloud Vision API 提供的功能:
-
标签检测
-
光学字符识别
-
手写识别
-
地标检测
-
物体定位
-
图像搜索
-
产品搜索
除了前面提到的功能,Cloud Vision 还允许我们提取给定图像的不同属性。下图展示了这个功能:
如我们所见,当提供一张图像时,Cloud Vision API 会自动提取其属性。你也可以通过访问cloud.google.com/vision/
来尝试这个功能。
我们之前在章节中提到过预训练模型这一术语。我们也看到,Cloud Vision API 让我们能够使用这些预训练模型。深入了解“预训练模型”这一术语非常值得,这有助于我们理解使用它们的重要性。
使用预训练模型的重要性
使用预训练模型通常被称为迁移学习。迁移学习并不是深度学习中非常基础的内容,它只是一种方法论。它并不指代某个特定的深度学习模型,但迁移学习的含义在深度学习中非常有效,尤其是在深度学习的背景下。
我们人类并不是从零开始学习每一项任务;我们尝试利用过去的经验来完成类似的任务。这就是迁移学习。我们倾向于将过去经验的知识转移到我们遇到的相似任务中。
但这如何应用于深度学习呢?让我们来看看。
当神经网络为某个特定任务进行训练时,它会试图估计最佳权重矩阵的值。现在,当你尝试为类似任务训练另一个网络时,事实证明你可以使用之前任务的权重。在这里,“相似性”的定义很宽泛,可以暂时忽略不提。但你可能会好奇,这里有什么优势。好吧,优势是多方面的,下面是几个例子:
-
你不需要从头开始训练神经网络,这为你节省了大量时间。
-
它利用了使用与你的任务相似的领域问题的最先进成果的机会。
在文献中,你使用网络权重的任务被称为源任务,你应用权重的任务被称为目标任务。你使用权重的网络模型被称为预训练模型。Goodfellow 等人在其著作《Deep Learning》中对迁移学习给出了非常微妙的定义:
“[A] 在一个环境中学到的知识被用来改善另一个环境中的泛化能力。”
迁移学习在自然语言处理(NLP)、计算机视觉等深度学习应用中已展现出卓越的成果。然而,迁移学习也有其局限性:
-
当源任务与迁移学习所用任务没有充分的相关性时,迁移学习可能会导致性能下降。
-
有时候很难确定从源任务到目标任务需要多少迁移。
为了深入研究迁移学习,推荐你阅读 Dipanjan 等人所著的《Hands-On Transfer Learning with Python》一书。接下来,我们将通过一个示例学习如何使用 Python 使用 Cloud Vision API。
设置 Vision Client 库
Cloud Vision API 通过一组适用于不同编程语言的库提供,这些库被称为 Vision Client 库。
该系列库中提供的一个库是 Python Cloud Vision Client 库,我们将在示例中使用它:
- 要安装 Python Cloud Vision Client 库,我们在终端中使用以下命令:
pip install --upgrade google-cloud-vision
强烈建议使用 Python 虚拟环境来安装 Vision Client 库。
-
安装完成后,我们需要设置一个服务账户来使用 API。
-
如前所述,设置服务账户的步骤如下:
-
打开 Google Cloud 控制台。
-
进入“APIs | Services | Credentials”。
-
点击“创建凭证”。
-
在下拉菜单中选择“新建服务账户”以选择服务账户。
-
为服务账户填写任何名称。
-
取消勾选“角色”。在使用 Cloud Vision API 时不需要此项设置。
-
点击“创建”。确认任何出现的警告框。
-
service account credentials
JSON 文件会被下载到你的计算机中。
-
-
现在,像之前一样,将下载的文件导出到系统环境中。为此,使用以下命令:
-
- 在 Linux(终端)中:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"
-
- 在 Windows(命令提示符)中:
set GOOGLE_APPLICATION_CREDENTIALS=/home/user/Downloads/service-account-file.json
-
在使用 Cloud Vision API 之前的最后一步,我们需要在创建服务账户的项目中启用该 API。为此,请执行以下操作:
-
在 Google Cloud 控制台的左侧导航面板中,点击“APIs 和 Services”。
-
点击“启用 APIs 和 Services”。
-
在出现的列表中找到 Cloud Vision API。
-
点击“启用”。
-
在此之后,我们准备好在脚本中使用 Python 调用 Cloud Vision API。
使用 Python 调用 Cloud Vision API
让我们创建一个新的 Python 脚本(或 Jupyter notebook)。为了使用 Cloud Vision API,我们首先需要导入 Cloud Vision 客户端库。
- 为此,我们使用以下代码:
from google.cloud import vision
- 有了这些,我们就准备好继续使用客户端库了。在我们的示例中,我们将对一张图像进行标注。图像标注服务是通过 Vision 库中的
imageAnnotatorClient()
函数提供的。我们将创建该方法的一个对象:
client = vision.ImageAnnotatorClient()
- 现在,让我们将要进行标注的文件加载到程序中:
with open("test.jpg", 'rb') as image_file:
content = image_file.read()
请注意,test.jpg
文件应该与工作目录中的其他文件放在同一位置,才能正常工作。
- 该文件目前是程序的原始二进制数据文件。为了让 Cloud Vision API 正常工作,我们需要将其转换为 Vision 客户端可以接受的图像类型:
image = vision.types.Image(content=content)
- 最后,我们通过 Cloud Vision API 调用 GCP 来对图像进行标注:
response = client.label_detection(image=image)
labels = response.label_annotations
打印由 Vision API 设置的标签后,我们将能够看到 Cloud Vision API 能够在提供的图片中检测到的所有可能的物体和特征,如下所示:
如果打印 labels
,结果应该如下所示:
预测的标签是 Sky
(天空)、Horizon
(地平线)、Atmosphere
(大气)、Sunrise
(日出)、Sunset
(日落)、Morning
(早晨)、Ocean
(海洋)、Calm
(平静)、Wing
(机翼)和 Evening
(傍晚)。
上述预测结果非常接近前面照片中捕捉到的实际场景。那是日出时分,从飞机的窗户拍摄的照片。
在 Python 中使用 Cloud Translation API
Cloud Translation API 帮助开发人员轻松将语言翻译功能集成到他们的应用程序中。它由先进的神经机器翻译技术提供支持,可以将其视为深度学习和机器翻译的结合体。Cloud Translation API 提供了使用预训练模型和构建生产级自定义模型的编程接口。
许多开发人员使用 Cloud Translation API 的预训练模型动态将给定文本翻译成目标语言。Cloud Translate API 支持 100 多种语言。但是这个语言库正在不断发展,以增强开发者社区的能力。以下截图展示了将一段英文文本翻译成孟加拉语的例子:
你可以随时在 cloud.google.com/translate/
上尝试这个功能。但是有时,给定文本的语言本身可能是未知的。Cloud Translation API 提供了一项叫做 标签检测 的服务来处理这种情况。
Cloud Translation API 的 AutoML 变体允许我们根据需要构建针对语言对(源语言和目标语言)的自定义模型。
设置 Cloud Translate API 以供 Python 使用
要在 Python 中使用 Cloud Translation API,首先我们需要安装 Google Cloud Translate Python 库。
- 为此,请在终端中使用以下
pip
命令:
pip install google-cloud-translate
-
现在,像之前一样,创建一个服务账户并下载凭证文件。将此文件导出到
GOOGLE_APPLICATION_CREDENTIALS
环境变量的路径中。 -
接下来,在启用的 API 列表中找到
Cloud Translate API
。完成后,我们就可以直接通过 Python 使用 GCP 进行翻译。
使用 Google Cloud Translation Python 库
创建一个新的 Jupyter 笔记本或 Python 脚本。接下来,我们将把 Google Cloud Translate API 导入到我们的项目中。
- 为此,请使用以下代码:
from google.cloud import translate_v2 as translate
- 我们需要创建一个 Cloud Translate API 对象来进行服务调用。我们可以按如下方式操作:
translate_client = translate.Client()
- 现在,让我们开始翻译过程。首先,我们需要一条要翻译的消息:
original = u'नमस्ते'
这将创建一个包含单词 Namaste(印度语问候语)的 Unicode 字符串。让我们看看它转换成英语是什么!
我们使用以下代码调用 API 将文本翻译成英语:
translation = translate_client.translate(original, target_language="en")
如果你观察 translation
变量,你会发现它包含了以下详细信息:
{
'translatedText': 'Hello',
'detectedSourceLanguage': 'hi',
'input': 'नमस्ते'
}
从这个字典中可以简单地推断出检测到的语言是印地语(用 hi
表示)。输入以其输入格式显示。translatedText
存储了 Hello
,这是 Namaste 的准确翻译。
总结
在本章中,我们探讨了 GCP 提供的一些著名且具有突破性的基于深度学习的服务。我们学习了如何使用 Python 的 Dialogflow 构建能够随着时间学习的对话型聊天机器人。我们使用 Cloud Vision API 来预测任何图像中识别到的物体。我们可以轻松地将此方法扩展到视频中,并获得类似的结果。最后,我们介绍了 Cloud Translate API,通过该服务进行深度基于 NLP 的翻译。GCP 提供的所有主要服务都可以通过 API 访问,这使得它们在任何项目中都易于替换。由高素质专业人员创建的模型的准确性值得称赞,这使得在构建 AI 驱动的 Web 解决方案时,Web 开发者的工作变得更加轻松。
在下一章中,我们将介绍亚马逊网络服务(AWS)提供的功能,使用 Python 将 AI 集成到 Web 应用程序中。
第十章:使用 Python 在 AWS 上进行 DL:对象检测和家庭自动化
我们熟悉了来自 Google Cloud Platform 的几个基于深度学习的产品,并学习了它们如何在第六章中使用 Python 在 Google Cloud Platform 上进行深度学习。现在,我们已经对云计算有了相当好的概述,在本章中,我们将介绍另一个云计算平台,Amazon Web Services(AWS),它也提供一些高性能和高可靠性的基于深度学习的解决方案,以便更轻松地解决问题。在本章中,我们将以 API 的形式介绍其中的两个,并学习如何从 Python 程序中使用它们。
我们将从设置 AWS 账户并在 Python 中配置 boto3 开始。然后,我们将学习如何在 Python 中使用 Rekognition API 和 Alexa API。
在本章中,我们将涵盖以下主题:
-
设置您的 AWS 账户
-
AWS 的简介
-
在 Python 中配置 boto3
-
使用 Python 中的 Rekognition API
-
使用 Python 中的 Alexa API
技术要求
您可以在github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter7
获取本章的代码。
要运行本章的代码,您需要以下软件:
-
Python 3.6+
-
Python PIL 库
其他安装步骤将在本章中介绍。
在 AWS 开始使用
在使用任何 AWS 服务或 API 之前,您必须创建您的 AWS 账户。在本节中,我们将快速浏览创建 AWS 账户的步骤:
- 首先,打开
aws.amazon.com/
。您应该会看到类似以下的页面:
- 然后点击“创建 AWS 账户”按钮,将带您到以下页面:
-
填写字段并点击“继续”。
-
门户将向您索取更多强制信息。它还将要求您注册支付方式以验证您的详细信息。
如果您不提供这些信息,您将无权使用 AWS 设施的免费层。
- 在注册的最后一步,您将被要求在三个计划(免费、开发者和商业)之间选择。根据您的需求选择适当的计划并继续。
像 Google Cloud Platform 一样,AWS 也提供免费层访问。首次注册 AWS 时,您可以免费使用多种 AWS 服务和产品,但只能使用一定的配额。您可以访问aws.amazon.com/free/
了解更多信息。
如果您按照上述步骤进行操作,应该会看到以下页面:
AWS 具有一个很棒的功能,可以为用户推荐解决方案和服务。为了充分利用此功能,您需要输入两项内容——您的角色和兴趣领域。您可以在上图中看到这一点。输入这两个信息并点击“提交”以获取一些定向的产品推荐。
- 下一步是点击“登录到控制台”按钮。
成功登录 AWS 控制台后,您应该会看到如下窗口:
AWS 控制台是您可以找到所有 AWS 提供的服务和解决方案的地方。可以随意通过点击“服务”标签浏览完整的服务集合。您也可以通过搜索框查找特定的服务。
到现在为止,我们的 AWS 账户应该已经准备好,可以开始动手了。在接下来的章节中,我们将简要回顾 AWS 的服务,以便更好地了解该平台。
AWS 服务简短概览
AWS 提供了多种领域的服务和解决方案。以下是 AWS 提供的不同类型的模块(括号中的为 AWS 提供的不同服务名称):
-
计算(EC2、Lambda 等)
-
存储(S3、Storage Gateway 等)
-
机器学习(Amazon SageMaker、AWS DeepLens 等)
-
数据库(RDS、DynamoDB 等)
-
迁移与传输(Snowball、DataSync 等)
-
网络与内容交付(CloudFront、VPC 等)
-
开发者工具(CodeStar、CodeCommit 等)
-
机器人技术(AWS RoboMaker)
-
区块链(Amazon Managed Blockchain)
-
分析(Athena、CloudSearch 等)
还有很多其他服务,如下图所示:
这个列表其实非常广泛,但我们暂时将焦点限制在机器学习(也称为深度学习)服务上。
AWS 控制台中的搜索框还允许您搜索可能已经听说过的 AWS API。让我们在其中输入 Rekognition
并按 Enter。您应该会看到 Rekognition 的主页,如下图所示:
我们将在本章稍后的部分详细探讨 Rekognition API。在接下来的章节中,我们将学习如何使用 boto3(一个提供 Python 编程接口的 AWS SDK)与不同的 AWS 资源进行交互。
使用 boto3 入门
boto3 是 AWS 官方提供的库,用于与 AWS API 通信。您可以在 aws.amazon.com/sdk-for-python/
找到该库,并可以通过以下命令安装:
pip install boto3
安装完成后,你需要配置 boto3 以供你的项目使用。配置 boto3 的第一步是从身份和访问管理(IAM)控制台获取你的 AWS 访问密钥。按照以下步骤进行配置:
- 访问你的 AWS IAM 控制台:
console.aws.amazon.com/iam
。页面应该如下所示:
在上面的仪表板中,你将能够看到访问密钥。
- 点击删除根访问密钥,然后点击管理安全凭证。系统将展示以下窗口:
- 展开访问密钥(访问密钥 ID 和秘密访问密钥)标签并从中获取访问密钥。密钥生成成功后,你应该看到如下信息:
- 下载密钥文件并将其保存在安全的位置,因为在配置 boto3 时需要用到此文件。
配置环境变量并安装 boto3
一旦你获得了访问密钥,创建两个环境变量,aws_access_key_id
和 aws_secret_access_key
。现在,根据你获得的密钥,设置它们的值。密钥包含的信息将帮助你区分密钥 ID 和秘密访问密钥。配置好必要的环境变量后,我们可以开始在 Python 中加载环境变量。
在 Python 中加载环境变量
一旦库成功安装,你可以通过以下代码行加载刚才创建的环境变量:
import os
aws_access_key_id= os.environ['aws_access_key_id']
aws_secret_access_key = os.environ['aws_secret_access_key']
一旦环境变量正确加载,我们就可以调用 boto3 来与 AWS 资源进行交互。例如,如果你想列出 AWS 账户中的 S3 存储桶,并将一张图片上传到某个存储桶。S3 是你想要访问的 AWS 资源。如果你在 AWS 账户中没有任何 S3 存储桶,不用担心,你可以快速创建一个。
创建 S3 存储桶
你可以通过以下步骤快速创建一个 S3 存储桶:
- 访问 S3 控制台主页:
s3.console.aws.amazon.com/s3
。页面应该如下所示:
- 点击创建存储桶。系统会要求你输入以下信息:
- 为你的存储桶命名,保持其他设置不变,然后点击创建。存储桶创建成功后,你将在 S3 控制台中看到它:
接下来,我们将学习如何使用 boto3 从 Python 代码访问 S3。
使用 boto3 从 Python 代码访问 S3
现在,你可以通过 Python 代码访问你的 S3 存储桶。以下代码行将展示可用的存储桶:
import boto3
s3 = boto3.resource(
's3',
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key
)
你在 resource()
的第一个参数中指定了你有兴趣访问 S3。你可以通过访问 bit.ly/2VHsvnP
来阅读相关文档。现在,你可以通过以下代码找到可用的存储桶:
for bucket in s3.buckets.all():
print(bucket.name)
你应该会得到一个列表作为输出。现在,假设你想将一张图片上传到某个存储桶。如果你想上传的图片就在当前工作目录中,以下代码行应将图像上传到指定的 S3 存储桶:
data = open('my_image.jpeg', 'rb')
s3.Bucket('demo-bucket-sayak').put_object(Key='my_image.jpeg', Body=data)
前面的代码包含了以下功能:
-
my_image.jpeg
是你想上传的图像路径。 -
在
Bucket()
方法中,指定的是你希望上传图片的 S3 存储桶的名称。
如果代码成功执行,你应该会收到以下输出:
s3.Object(bucket_name='demo-bucket-sayak', key='my_image.jpeg')
你可以通过访问 AWS S3 控制台,进入你上传图像的存储桶,来验证图像是否上传成功。你应该能在其中看到类似以下内容:
现在你已经成功在 Python 中配置了 boto3,我们可以继续学习如何使用 boto3 在 Python 中调用 Rekognition 和 Alexa API。
在 Python 中使用 Rekognition API
Amazon Rekognition 是一个启用了深度学习的视觉分析服务,可以帮助你无缝搜索、验证和分析数十亿张图像。首先让我们简单回顾一下 Rekognition API,然后我们将直接开始在 Python 中使用它。首先访问 Rekognition API 的主页 console.aws.amazon.com/rekognition/home
。我们已经在本章的早些章节中看过 Rekognition 的主页。
正如你可能已经从导航栏中注意到的,Rekognition API 提供了很多功能:
-
物体和场景检测:此功能可以自动为给定图像标注物体、标签和场景,并提供置信度分数。
-
图像审核:这个功能可以帮助你检测图像中的显性或暗示性成人内容,并提供置信度分数。
-
名人识别:使用这个功能,你可以自动识别图像中的名人,并提供置信度分数。
-
人脸比对:这个功能可以用于查看两个面孔的相似度百分比,以判断它们之间的匹配程度。
除了这些功能外,它还具有更多其他功能。
Rekognition API 提供的解决方案已经被许多组织证明是非常有用的,因为它们真正解决了一些现实世界中的挑战性问题。你可以通过点击 API 主页上相应的解决方案,快速尝试列表中提到的任何解决方案。我们来试试名人识别解决方案。
首先,访问 console.aws.amazon.com/rekognition/home?region=us-east-1#/celebrity-detection
(注意区域可能有所不同)。它应当看起来像下面的图片:
门户将允许你上传自己的图片并进行测试。让我们测试我的图片(我们本可以使用名人媒体图片,但这些图片有版权保护)。你可以看到预期的结果:
也可以尝试其他解决方案。现在让我们看看如何从 Python 代码中使用 Rekognition API:
- 创建一个新的 Jupyter Notebook。首先,你需要创建一个新的 Jupyter notebook,例如命名为
Sample.ipynb
。你还需要提供一张你希望测试的图像,用于通过 AWS Rekognition API 进行名人识别,下面是 Jupyter 目录结构截图:
- 导入 AWS 账户凭证的环境变量。你需要将之前在 boto3 配置部分获得的账户凭证导入到脚本中。为此,可以使用以下代码:
import os
aws_access_key_id= os.environ['aws_access_key_id']
aws_secret_access_key = os.environ['aws_secret_access_key']
- 使用 boto3 创建 AWS Rekognition API 客户端。我们现在准备实例化一个 boto3 Rekognition API 客户端对象。为此,我们需要将要使用的 API 和所需的 AWS 区域名称传递给
boto3
对象。还需要传入之前步骤中获取的凭证,代码如下:
import boto3
client=boto3.client('rekognition', region_name='us-east-1', aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key)
- 从磁盘读取图像并将其传递给 API。使用 boto3 SDK 将文件传递给 AWS API 有两种方法。首先,你可以直接从你有权限的 S3 存储桶发送它们,或者可以将图像作为
Bytes
数组从本地磁盘发送。我们已经在前面的部分展示了如何从 S3 存储桶查找图像。
现在我们将展示一个示例,其中我们从本地磁盘获取多张图片并传递给 API 调用:
- 首先,使用 Python 的原生方法将图像读取到一个变量中,代码如下:
image = open("image.jpg", "rb")
- 现在,通过我们之前实例化的客户端将其传递给 API,使用以下代码行:
response = client.recognize_celebrities(Image={'Bytes':image.read()})
- 观察响应。一旦 API 调用成功,你的
response
变量将保存 API 返回的信息。要查看该信息,可以打印该变量:
{'CelebrityFaces': [{'Urls': ['www.imdb.com/name/nm1682433'],
'Name': 'Barack Obama',
'Id': '3R3sg9u',
'Face': {'BoundingBox': {'Width': 0.3392857015132904,
'Height': 0.27056020498275757,
'Left': 0.324404776096344,
'Top': 0.06436233967542648},
'Confidence': 99.97088623046875,
'Landmarks': [{'Type': 'eyeLeft',
'X': 0.44199424982070923,
'Y': 0.17130307853221893},
{'Type': 'eyeRight', 'X': 0.5501364469528198, 'Y': 0.1697501391172409},
{'Type': 'nose', 'X': 0.4932120144367218, 'Y': 0.2165488302707672},
{'Type': 'mouthLeft', 'X': 0.43547138571739197, 'Y': 0.25405779480934143},
{'Type': 'mouthRight', 'X': 0.552975058555603, 'Y': 0.2527817189693451}],
'Pose': {'Roll': -1.301725149154663,
'Yaw': -1.5216708183288574,
'Pitch': 1.9823487997055054},
'Quality': {'Brightness': 82.28946685791016,
'Sharpness': 96.63640594482422}},
'MatchConfidence': 96.0}],
'UnrecognizedFaces': [],
'ResponseMetadata': {'RequestId': 'ba909ea2-67f1-11e9-8ac8-39b792b4a620',
'HTTPStatusCode': 200,
'HTTPHeaders': {'content-type': 'application/x-amz-json-1.1',
'date': 'Fri, 26 Apr 2019 07:05:55 GMT',
'x-amzn-requestid': 'ba909ea2-67f1-11e9-8ac8-39b792b4a620',
'content-length': '813',
'connection': 'keep-alive'},
'RetryAttempts': 0}}
API 识别我们的图像为 Barack Obama(巴拉克·奥巴马)。它还提供了许多其他有用的信息,例如面部匹配的 BoundingBox
(边界框)、预测的 Confidence
(置信度)、眼睛、嘴巴和鼻子的位置信息等。我们可以使用这些信息对图像进行进一步操作——例如,简单地裁剪出匹配的部分。
- 获取图像的匹配部分。为了准备图像中已识别区域的裁剪版本,我们可以使用以下代码:
from PIL import Image
from IPython.display import display
im=Image.open('image.jpg')
w, h = im.size
celeb = response['CelebrityFaces'][0]['Face']['BoundingBox']
x1 = (celeb["Left"])*w
y1 = (celeb["Top"])*h
x2 = (celeb["Left"] + celeb["Width"])*w
y2 = (celeb["Top"] + celeb["Height"])*h
box=(x1,y1,x2,y2)
im1=im.crop(box)
display(im1)
你应该会看到以下图像作为最终结果,这是 API 生成的用于进行名人识别的边界框:
进一步探索 AWS 的 boto3 API 后,你会发现它能够处理所有 AWS 服务,而不仅仅是 Rekognition API。这意味着,根据 API 规范要求,前面的示例代码几乎可以用于所有可用的 API,只需做一些小修改。
在接下来的部分,我们将介绍 Alexa,这是亚马逊推出的旗舰产品,用于构建语音接口,其功能范围从聊天机器人到虚拟个人助手不等。我们将学习如何使用 Alexa 构建一个简单的家庭自动化解决方案。
在 Python 中使用 Alexa API
亚马逊 Alexa 是由亚马逊开发的基于语音的个人助手。该产品最初作为 Amazon Echo 设备的接口推出,后来启发了 Google 推出的 Google Home 设备,后者使用 Google Assistant。Alexa 的其他竞争者包括微软的 Cortana 和苹果的 Siri。作为一个虚拟助手,Alexa 可以轻松进行拨打电话、安排会议或播放歌曲等操作。Alexa 能执行的各种任务被称为技能,我们将在本节中使用这一术语。
Alexa 中的技能是将功能引入平台的核心。每个技能需要通过 Alexa 的主要接口调用,然后该技能将接管整个功能,直到程序逻辑完成或用户明确要求技能结束。技能应用了执行任务所需的逻辑,因此该逻辑需要存储在某个地方,可能还需要与数据库和执行运行时一起存储。虽然许多技能托管在多个服务上,例如 Heroku、PythonAnywhere、GCP 等,但在 AWS Lambda 函数中托管技能和逻辑代码是非常常见的。
在本节中,我们将使用 Alexa 的 Python SDK 创建一个示例的家庭自动化 Alexa 技能,并将其托管在 AWS Lambda 上。
项目所需的前提条件和框架图
在你开始构建 Alexa 技能之前,你需要在 AWS 和 Amazon Developer 分别拥有以下两种账户:
-
一个 AWS 账户(免费套餐可用)—aws.amazon.com
-
一个 Amazon 开发者账户(免费)—developer.amazon.com
一旦你创建了这些账户——这部分内容超出了本书的范围——你就可以继续创建我们的家庭自动化技能了。我们将要创建的家庭自动化技能架构如下面的框架图所示:
在构建此技能时,我们将使用以下服务,你可以通过访问以下列表中的链接了解更多信息:
-
Amazon Alexa Skills Kit:
developer.amazon.com/alexa-skills-kit
-
使用 Amazon 登录:
developer.amazon.com/docs/login-with-amazon/minitoc-lwa-overview.html
-
AWS CloudWatch:
aws.amazon.com/cloudwatch/
-
Amazon DynamoDB:
aws.amazon.com/dynamodb/
-
AWS Lambda:
aws.amazon.com/lambda/
为技能创建配置
技能需要在服务之间建立一定的连接才能工作。此外,部署在 AWS Lambda 上的技能逻辑需要配置为可以在 Alexa 上使用。请在工作文件夹的根目录中创建一个名为setup.txt
的文件,内容如下。随着我们逐步完成本节中的步骤,我们将不断添加内容:
[LWA Client ID]
amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[LWA Client Secret]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Alexa Skill ID]
amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
[AWS Lambda ARN]
arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:skill-sample-language-smarthome-switch
[APIs]
https://pitangui.amazon.com/api/skill/link/XXXXXXXXXXXXXX
https://layla.amazon.com/api/skill/link/XXXXXXXXXXXXXX
https://alexa.amazon.co.jp/api/skill/link/XXXXXXXXXXXXXX
在接下来的各个部分中,我们将提到此文件为setup.txt
。它实际上只包含有关你的技能的信息。你也可以在其他文本编辑器中实现此文件,例如 Google 文档。
设置“使用 Amazon 登录”
对于家庭自动化技能,你需要启用“使用 Amazon 登录”服务。为此,请按照以下步骤操作:
- 访问
developer.amazon.com/lwa/sp/overview.html
。你将看到如下截图所示的页面:
-
点击页面加载后的“创建新的安全配置文件”按钮。
-
将安全配置文件名称设置为
Smart Home Automation Profile
。 -
提供配置文件的描述。
-
对于内容隐私声明 URL,你需要一个有效的隐私政策网页,以便将技能推向生产环境。创建并托管隐私政策,并在此字段中提供该链接。创建隐私政策的一个非常实用的工具可以在
app-privacy-policy-generator.firebaseapp.com/
找到。 -
点击“保存”。
-
在下一个页面中,点击齿轮菜单中的“安全配置文件”选项。你将被带到“安全配置文件管理”页面,如下图所示:
-
在安全配置文件列表中,点击“Web 设置”选项卡,显示“客户端 ID 和客户端密钥”链接以供家庭自动化配置文件使用。
-
复制显示的客户端 ID 和客户端密钥值,并将其保存到工作目录中的
setup.txt
文件中,分别替换[LWA Client ID]
和[LWA Client Secret]
格式示例条目。
保持此标签页打开,以便进行后续步骤。在新的浏览器标签页中查看下一部分的步骤。
创建技能
现在我们可以继续创建技能:
- 登录
developer.amazon.com/alexa/console/ask
开始该过程。你将看到一个类似于以下的页面:
-
点击创建技能(Create Skill)。
-
将名称设置为
Home Automation Skill
,或者选择你喜欢的名称。 -
在“选择一个模型添加到你的技能”部分,点击智能家居模型(Smart Home model)。你的选择现在应该类似于以下内容:
-
点击创建技能(Create Skill)以完成技能创建的初始阶段。
-
在下一个页面中,你将看到技能 ID。将此技能 ID 复制到本地工作目录中的
setup.txt
文件。
不要关闭此标签页,因为这里还有字段需要填写。打开一个新的浏览器标签页,在下一部分继续工作。
配置 AWS Lambda 函数
在我们将 Lambda 函数的 ARN 添加到技能端点配置之前,我们必须先为 Lambda 函数创建配置。你可以通过以下步骤完成:
- 访问
console.aws.amazon.com/iam/home#/policies
,你将看到一个类似于以下截图的页面:
-
点击创建策略(Create policy)。
-
在创建策略编辑器的 JSON 选项卡中输入以下 JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"dynamodb:UpdateItem",
"logs:CreateLogGroup",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
-
点击审核策略(Review policy),并将策略的名称设置为
HomeAutomationPolicy
。 -
点击创建策略(Create policy)。
-
接下来,在页面的左侧导航菜单中,点击角色(Roles)。
-
点击创建角色(Create role)。
-
选择 AWS 服务和 Lambda,然后点击下一步:权限(Next: Permissions)。
-
在过滤字段中搜索 HomeAutomationPolicy,勾选该策略。你的屏幕应该类似于以下内容:
-
点击下一步:标签(Next: Tags)。
-
点击下一步:审核(Next: Review)。
-
将角色名称设置为
lambda_home_automation
。 -
点击创建角色(Create role)。
现在我们来创建 Lambda 函数。
创建 Lambda 函数
配置好 Lambda 函数的适当设置后,我们现在可以创建 Lambda 函数本身。为此,请在 AWS 控制台中,访问console.aws.amazon.com/lambda/home
,并按以下步骤操作:
-
点击创建函数(Create function)。
-
将函数名称设置为
homeAutomation
。 -
选择
Python 3.6
运行时。 -
在执行角色中的现有角色下,从下拉菜单中选择
lambda_home_automation
角色。 -
点击
创建函数
(Create function)。 -
复制下一个页面中显示的 Lambda ARN,该页面上有一个恭喜创建 Lambda 函数的消息。将此 ARN 放入我们本地工作目录的
setup.txt
文件中的[AWS Lambda ARN]字段。此时,屏幕应该类似于以下截图:
请注意,您屏幕上显示的触发器和目标可能与前面的截图有所不同。
- 在左侧导航栏中,点击“添加触发器”,弹出 Lambda 函数的可用触发器下拉列表,如下截图所示:
-
点击 Alexa Skills Kit 以打开该触发器的配置对话框。
-
在技能 ID 字段中粘贴 Alexa 技能 ID。我们之前已将此值存储在
setup.txt
中,它的形式类似于amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
。 -
点击“添加”以添加触发器,并返回 Lambda 函数管理屏幕。
-
点击页面右上角的“保存”。
完成最后一步后,触发器部分将显示已连接的 Alexa 技能的详细信息。如果没有显示,您应该检查是否正确按照前述步骤操作。
配置 Alexa 技能
现在,我们需要配置我们在浏览器的另一个标签页中保持打开的技能。我们将通过以下步骤进行配置:
-
返回该标签页并在默认端点字段中填写 Lambda 函数的 ARN。
-
点击“保存”。
-
点击页面底部的“设置账户链接”。
-
对于授权 URL,输入
https://www.amazon.com/ap/oa
。 -
对于访问令牌 URL,输入
https://api.amazon.com/auth/o2/token
。 -
对于客户端 ID 字段,复制
[LWA Client ID]
来自setup.txt
文件。 -
对于客户端密钥字段,复制
[LWA Client Secret]
来自setup.txt
文件。 -
点击“添加作用域”,并输入
profile:user_id
。 -
复制页面底部的重定向 URL,并将其粘贴到
setup.txt
文件中的[APIs]部分。这些 URL 类似于:
-
点击“保存”。
-
在安全配置管理的浏览器标签页中,点击“Web 设置”标签。
-
点击“编辑”,并将三个重定向 URL 添加到“允许返回 URL”字段中。您需要点击“添加另一个”以输入多个 URL。
-
点击“保存”。
现在,让我们为技能设置 Amazon DynamoDB。
为技能设置 Amazon DynamoDB
为了让技能能够保存用户数据,它需要一个数据库。我们将使用 Amazon DynamoDB 服务。设置此服务的步骤如下:
-
点击“创建表格”按钮。
-
输入表格名称为
SmartHome
。 -
对于主键,输入
ItemId
。 -
保持所有默认设置不变,然后点击“创建”。此时,您的屏幕应该显示如下截图:
然后,您可以进入 DynamoDB 控制台,查看刚创建的表格;不过,这可能需要几分钟。
部署 AWS Lambda 函数的代码
剩下的就是最后一个设置部分——为 AWS Lambda 函数提供逻辑的代码。进入你的 Lambda 函数配置页面,并向下滚动至编辑器。
你会注意到编辑器有一个两栏界面:左侧栏显示 Lambda 函数存储中的文件,右侧栏则可以编辑这些文件,具体如下截图所示:
点击 lambda_function.py
开始编辑文件,并按以下步骤进行操作:
- 导入必要的模块。为了使函数正常工作,我们需要一些常见库的支持,具体代码如下:
import boto3
import json
import random
import uuid
import time
boto3 API 用于连接我们设置的 Amazon DynamoDB 实例。JSON 模块帮助生成 Alexa 技能的响应。其余模块帮助生成响应内容。
- 创建
AlexaResponse
类。为了能够完全复制 Alexa 技能期望的响应格式,我们可以快速设置一个辅助类,用于生成 Lambda 函数调用的响应。我们将其命名为AlexaResponse
;类的初始化如下代码片段所示:
class AlexaResponse:
def __init__(self, **kwargs):
self.context_properties = []
self.payload_endpoints = []
# Set up the response structure
self.context = {}
self.event = {
'header': {
'namespace': kwargs.get('namespace', 'Alexa'),
'name': kwargs.get('name', 'Response'),
'messageId': str(uuid.uuid4()),
'payloadVersion': kwargs.get('payload_version', '3')
},
'endpoint': {
"scope": {
"type": "BearerToken",
"token": kwargs.get('token', 'INVALID')
},
"endpointId": kwargs.get('endpoint_id', 'INVALID')
},
'payload': kwargs.get('payload', {})
}
if 'correlation_token' in kwargs:
self.event['header']['correlation_token'] = kwargs.get('correlation_token', 'INVALID')
if 'cookie' in kwargs:
self.event['endpoint']['cookie'] = kwargs.get('cookie', '{}')
if self.event['header']['name'] == 'AcceptGrant.Response' or self.event['header']['name'] == 'Discover.Response':
self.event.pop('endpoint')
前面的 AlexaResponse
类初始化方法设置了预期的输出格式及各种常量设置,比如有效负载的版本号,以及一些输出对象的基本验证。接下来,我们创建了添加内容属性的方法和用于设置响应中 cookies 的另一个方法。最后,另一个方法被添加用于设置有效负载的端点:
def add_context_property(self, **kwargs):
self.context_properties.append(self.create_context_property(**kwargs))
def add_cookie(self, key, value):
if "cookies" in self is None:
self.cookies = {}
self.cookies[key] = value
def add_payload_endpoint(self, **kwargs):
self.payload_endpoints.append(self.create_payload_endpoint(**kwargs))
-
现在来定义我们在上一阶段创建的三个处理方法。上一阶段声明的方法依赖于它们自己的内部方法。这些大多是辅助函数,与本章的主要内容关系不大,因此我们将把这些留给你根据 AWS Lambda 函数和 Alexa 技能的响应体文档自行实现。可以参考我们本章代码库中的示例实现,位于
lambda_function.py
文件的第 65 行至 102 行,链接:tiny.cc/HOPDLW_CH7_lfpy
。 -
接下来,我们将设置方法以生成
AlexaResponse
类的最终响应。最后,我们创建方法将所有不同的部分——上下文、事件、有效负载、端点和 cookies——合并为一个准备好与 Alexa 技能交互的对象:
def get(self, remove_empty=True):
response = {
'context': self.context,
'event': self.event
}
if len(self.context_properties) > 0:
response['context']['properties'] = self.context_properties
if len(self.payload_endpoints) > 0:
response['event']['payload']['endpoints'] = self.payload_endpoints
if remove_empty:
if len(response['context']) < 1:
response.pop('context')
return response
def set_payload(self, payload):
self.event['payload'] = payload
def set_payload_endpoint(self, payload_endpoints):
self.payload_endpoints = payload_endpoints
def set_payload_endpoints(self, payload_endpoints):
if 'endpoints' not in self.event['payload']:
self.event['payload']['endpoints'] = []
self.event['payload']['endpoints'] = payload_endpoints
AlexaResponse
类现在已完成。接下来我们将使用以下代码连接到 DynamoDB 服务:
aws_dynamodb = boto3.client('dynamodb')
- 接下来,我们定义文件的主要方法和入口点——
lambda_handler
方法:
def lambda_handler(request, context):
# JSON dump for the request
print('Request: ')
print(json.dumps(request))
if context is not None:
print('Context: ')
print(context)
我们将继续为此步骤的其余部分添加到前面的代码中。在前面的代码行中,我们声明了 lambda_handler
方法,该方法接受来自 Alexa 技能的 request
和 context
对象。然后,它对请求进行 JSON 转储,以便我们可以稍后从 Amazon CloudWatch 仪表板观察它。接下来,它还对请求中附加的上下文进行转储(如果有的话):
# Validate we have an Alexa directive
if 'directive' not in request:
aer = AlexaResponse(
name='ErrorResponse',
payload={'type': 'INVALID_DIRECTIVE',
'message': 'Missing key: directive, Is the request a valid Alexa Directive?'})
return send_response(aer.get())
我们接下来验证请求中是否包含有效的 Alexa 指令,如果没有找到,则生成错误信息并作为响应返回。请注意这里使用了 AlexaResponse
类对象。我们将来会使用它来生成来自该脚本的响应:
# Check the payload version
payload_version = request['directive']['header']['payloadVersion']
if payload_version != '3':
aer = AlexaResponse(
name='ErrorResponse',
payload={'type': 'INTERNAL_ERROR',
'message': 'This skill only supports Smart Home API version 3'})
return send_response(aer.get())
类似地,还进行另一个检查,以确保请求的负载版本是 3。这是因为我们只为 Alexa 的智能家居 API 版本 3 开发了它:
- 首先,我们打开请求并查看请求了什么:
name = request['directive']['header']['name']
namespace = request['directive']['header']['namespace']
- 然后,我们根据
namespace
处理来自 Alexa 的请求。请注意,这个示例接受任何grant
请求,但在您的实现中,您将使用代码和令牌来获取并存储访问令牌:
if namespace == 'Alexa.Authorization':
if name == 'AcceptGrant':
grant_code = request['directive']['payload']['grant']['code']
grantee_token = request['directive']['payload']['grantee']['token']
aar = AlexaResponse(namespace='Alexa.Authorization', name='AcceptGrant.Response')
return send_response(aar.get())
前面的条件作用于 Alexa 授权请求。
- 对于发现和关闭开关的操作,我们使用以下代码:
if namespace == 'Alexa.Discovery':
if name == 'Discover':
adr = AlexaResponse(namespace='Alexa.Discovery', name='Discover.Response')
capability_alexa = adr.create_payload_endpoint_capability()
capability_alexa_powercontroller = adr.create_payload_endpoint_capability(
interface='Alexa.PowerController',
supported=[{'name': 'powerState'}])
adr.add_payload_endpoint(
friendly_name='Sample Switch',
endpoint_id='sample-switch-01',
capabilities=[capability_alexa, capability_alexa_powercontroller])
return send_response(adr.get())
if namespace == 'Alexa.PowerController':
endpoint_id = request['directive']['endpoint']['endpointId']
power_state_value = 'OFF' if name == 'TurnOff' else 'ON'
correlation_token = request['directive']['header']['correlationToken']
这个示例始终返回 success
响应,无论是 TurnOff
还是 TurnOn
请求。
- 现在,我们在设置状态时检查是否有错误:
state_set = set_device_state(endpoint_id=endpoint_id, state='powerState', value=power_state_value)
if not state_set:
return AlexaResponse(
name='ErrorResponse',
payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get()
apcr = AlexaResponse(correlation_token=correlation_token)
apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value)
return send_response(apcr.get())
-
最后,我们提取指令名称和指令的命名空间,以确定要发送回的响应类型。根据发送的指令,生成不同的响应,并最终通过
AlexaResponse
类对象发送回去。 -
注意前一步代码中
send_response
方法的使用。我们需要定义这个方法。它的任务是以 JSON 格式发送AlexaResponse
对象,并将其记录以便在 Amazon CloudWatch 中观察:
def send_response(response):
print('Response: ')
print(json.dumps(response))
return response
- 更新
device state
方法。由于我们正在为使用 Alexa 的简单开关设备构建自动化,我们需要维护开关的状态信息。我们通过将其状态存储在 DynamoDB 中来实现这一点。我们将为此添加一个更新方法,如下所示:
def set_device_state(endpoint_id, state, value):
attribute_key = state + 'Value'
response = aws_dynamodb.update_item(
TableName='SmartHome',
Key={'ItemId': {'S': endpoint_id}},
AttributeUpdates={attribute_key: {'Action': 'PUT', 'Value': {'S': value}}})
print(response)
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
return True
else:
return False
接下来,我们将测试 Lambda 函数。
测试 Lambda 函数
现在,我们可以检查函数是否正确响应。为此,我们必须通过以下步骤在 Lambda 函数的仪表板中创建一个测试:
-
在我们在前面创建的 Lambda 函数页面的右上角,点击 Test。
-
将弹出一个对话框,提供写入新测试或使用现有测试的选项。选择“创建新测试事件”选项。
-
在事件模板中,确保选择了 Hello World。
-
接下来,提供
directiveDiscovery
的事件名称。 -
在编辑器中输入以下 JSON:
{
"directive": {
"header": {
"namespace": "Alexa.Discovery",
"name": "Discover",
"payloadVersion": "3",
"messageId": "1bd5d003-31b9-476f-ad03-71d471922820"
},
"payload": {
"scope": {
"type": "BearerToken",
"token": "access-token-from-skill"
}
}
}
}
此时,您的屏幕应该类似于以下内容:
-
向下滚动并点击 Create。
-
返回 Lambda 函数仪表板后,在右上角,从下拉菜单中选择
directoryDiscover
测试。 -
点击测试(Test)。
完成后,测试将显示响应状态和 Lambda 函数的响应。你可以在 Lambda 函数仪表板顶部的页面上看到结果,结果会类似以下截图:
如果测试失败,请确保你已经仔细按照前述步骤操作,并确保各个服务所在的区域一致。
测试 AWS Home Automation 技能
作为本项目的最后阶段,我们将在 Alexa 测试模拟器中测试我们的技能。为此,请按照以下步骤操作:
-
访问
alexa.amazon.com
并登录。 -
在左侧菜单中点击技能(Skills)。
-
点击页面右上角的“你的技能”(Your Skills)。
-
选择 DEV SKILL 标签。
-
点击 HomeAutomationSkill。你应该会看到以下界面:
-
点击启用按钮。系统会要求你允许访问你的开发者账户权限。
-
返回到 Alexa 开发者控制台,点击发现设备(Discover devices)。一个名为 Sample Switch 的新设备将会显示为可用,如下图所示:
-
现在,进入 Alexa Skills Kit 开发页面的测试(Test)选项卡,选择 HomeAutomation 技能。
-
在模拟器中输入
alexa, turn on the sample switch
。如果请求被接受,你将收到 Alexa 的OK
响应,如下图所示:
为了检查技能是否正常工作,你可以进入你的 DynamoDB 表格 SmartHome,并切换到表格的 Items 选项卡。你应该能看到以下记录:
恭喜你成功构建了一个简单的 Home Automation 技能!你可以尝试使用这个技能并为 Alexa 创建你自己的家庭自动化技能。当你准备好将其发布给更广泛的观众时,可以参考 developer.amazon.com/docs/alexa-for-business/create-and-publish-private-skills.html
上的文档进行操作。
总结
在本章中,我们介绍了如何使用其 Python API——boto3 来使用 AWS。我们探索了使用该 API 的各种选项和配置要求,并通过一个示例展示了如何使用它与 Rekognition API 进行名人识别。接着,我们深入探讨了如何为家庭自动化创建 Alexa 技能,设置一个简单的开关开/关任务。这可以很容易地推广到其他智能家居设备。我们还了解了如何将 Alexa 技能逻辑托管在 AWS Lambda 上,并通过 AWS CloudWatch 进行观察。我们还探索了将动态设备数据存储在 Amazon DynamoDB 中的方法。
在接下来的章节中,我们将看到如何在微软的 Azure 平台上使用 Python 进行深度学习。
第十一章:使用 Python 在微软 Azure 上进行深度学习
本章将结束我们的云 API 探索之旅。到目前为止,我们已经轻松地了解了 API 的精彩世界,特别是那些让我们轻松进行深度学习的 API。我们已经看到了如何使用 REST API 并通过编程方式使用它们。像谷歌云平台(GCP)和亚马逊云服务(AWS)一样,微软也提供了自己的云服务平台,称为 Azure。与前几章一样,我们将重点介绍 Azure 提供的基于深度学习的解决方案。我们将稍微调整一下方向,还将介绍微软的认知工具包(CNTK),它是一个类似于 Keras 的深度学习框架。
本章我们将涵盖以下主题:
-
在 Azure 中设置你的账户
-
快速了解 Azure 提供的深度学习解决方案
-
在 Python 中使用人脸 API
-
在 Python 中使用文本分析 API
-
CNTK 简介
技术要求
你可以从github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter8
访问本章的代码。
为了运行本章使用的代码,你需要以下软件:
-
Python 3.6+
-
Python PIL 库
-
Matplotlib 库
所有其他安装步骤,如 CNTK 和 Django,将在本章中进一步介绍。
在 Azure 中设置你的账户
根据你之前使用云平台的经验,你可能已经意识到,所有一切都始于在云服务提供商处设置账户和账单。这是一个相当标准的工作流程,Azure 也不例外。因此,让我们前往azure.microsoft.com
,并按照以下步骤操作:
- 点击“开始免费试用”按钮,如下所示:
请注意,你需要一个微软账户才能继续进行以下步骤。如果你没有账户,可以在account.microsoft.com/account
创建一个。
- 你将被重定向到另一个页面,页面中会再次显示一个“开始免费试用”按钮。点击它。
- 你将被要求登录微软账户才能继续。根据提示输入账户信息,应该能看到如下截图的页面:
如果你是首次使用,你将获得价值 200 美元的信用额度(根据你的货币不同),可以免费使用 30 天,探索 Azure 提供的不同服务。
- 填写你的详细信息,包括通过银行卡验证身份。
你可能需要支付一笔非常小的费用。务必查看 Azure 免费套餐的条款和条件,详情请见 azure.microsoft.com/en-in/offers/ms-azr-0003p/
。
一旦这个过程完成,你就已经设置好了,可以进入你的 Azure 门户 (portal.azure.com
),它的功能与前几章中提到的 GCP 和 AWS 控制台类似。
Azure 门户界面如下所示:
现在你已经设置好了 Azure 账户,让我们在下一节中探索 Azure 基于深度学习的服务。
对 Azure 提供的深度学习服务的逐步讲解
Azure 的深度学习(以及一般的机器学习)服务大致分为三大部分:
- Azure 机器学习服务 (
azure.microsoft.com/en-in/services/machine-learning-service/
),提供完整的机器学习生命周期,包括模型构建、训练和部署:
- 机器学习 API (
gallery.azure.ai/machineLearningAPIs
),提供广泛的学习任务 API,例如内容审核、翻译、异常检测等:
- Azure AI (
azure.microsoft.com/en-in/overview/ai-platform/
),专注于知识挖掘、决策挖掘以及计算机视觉和语言建模领域的其他许多类似机器学习能力:
接下来,我们将分别研究两个 API,一个用于计算机视觉任务,一个用于自然语言理解任务。我们还将学习如何从 Python 中使用这些 API。让我们深入了解。
使用 Face API 和 Python 进行物体检测
物体检测是计算机视觉的经典应用,广泛应用于许多现实世界问题中,如视频监控系统。在本节中,我们将使用 Face API 从给定图像中检测面孔。这在设计视频监控系统时有直接应用。你可以通过 azure.microsoft.com/en-us/services/cognitive-services/face/
了解更多关于 Face API 的信息。
初始设置
Azure 还允许你在 7 天内免费试用该 API。但是,由于你已经拥有 Azure 账户(假设你有免费的信用额度),我们可以采用另一种方式,如下所示:
-
登录你的 Azure 账户。
-
访问
azure.microsoft.com/en-us/services/cognitive-services/face/
。 -
点击“已经在使用 Azure?现在免费试用此服务”。
现在您应该看到如下截图的窗口:
- 填写相关信息并点击“创建”完成。您将看到一个弹出窗口,显示“正在提交部署”。
部署完成后,您应该看到如下截图的页面:
- 点击“前往资源”,您将被重定向到资源页面,其中包含许多详细信息:
只需稍微向下滚动,您就可以看到 Face API 的端点。请注意,它会根据您在创建部署时输入的配置细节而有所不同。该端点看起来像这样:eastus.api.cognitive.microsoft.com/face/v1.0
。请记下它。
现在,为了能够通过编程使用 Face API,您需要创建相应的 API 密钥。在同一页面的顶部,有一个部分写着获取您的密钥:
- 在该部分下,点击“密钥”,您将看到类似如下截图的内容:
现在您已经拥有了 Face API 的 API 密钥,准备好使用它了。
从 Python 代码中调用 Face API
当您的程序包含诸如 API 密钥之类的安全凭证时,通常的最佳做法是将这些密钥定义为环境变量,并在程序中调用它们。因此,创建一个环境变量来存储 Face API 的一个 API 密钥。
要将环境变量添加到计算机中,可以参考这篇文章:www.twilio.com/blog/2017/01/how-to-set-environment-variables.html
。
就我而言,我将环境变量命名为 face_api_key
。您可以上传任何包含面孔的图像。对于这个示例,我将使用此图像:
创建一个新的 Jupyter Notebook 并按照以下步骤操作:
- 现在,使用 Python 加载环境变量,如下所示:
import os
face_api_key = os.environ['face_api_key']
-
现在,将您的 Face API 端点(用于物体检测)分配给一个变量。
-
同时,将您想要测试的图像上传到一个在线文件服务器,如 Imgur,并获取允许从 Imgur 提取原始图像的 URL。
就我而言,我已将图像上传到 GitHub 仓库,并使用相应的 URL:
face_api_url = 'https://eastus.api.cognitive.microsoft.com/face/v1.0/detect'
image_url= 'https://raw.githubusercontent.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/master/Chapter8/sample_image.jpg'
请注意,在前面的 API 中,URL 末尾的端点名称会发生变化。在大多数情况下,端点名称前面的部分在您使用认知服务时会保持不变,除非 Azure 平台本身要求进行更改。
- 现在,导入
requests
模块,并按如下所示设置 API 负载:
import requests
params = {
'returnFaceId': 'true',
'returnFaceLandmarks': 'false',
'returnFaceAttributes': 'age,gender',
}
- 现在,我们已经准备好向 Face API 发出请求。
以下代码行将为您执行此操作:
# Define the header param
headers = { 'Ocp-Apim-Subscription-Key': face_api_key }
# Define the body params
params = {
'returnFaceId': 'true',
'returnFaceLandmarks': 'false',
'returnFaceAttributes': 'age,gender',
}
- 我们现在可以显示从 API 接收到的响应:
# Make the call to the API
response = requests.post(face_api_url, params=params, headers=headers, json={"url": image_url})
# Get the response and log
faces = response.json()
print('There are {} faces im the given image'.format(str(len(faces))))
在这种情况下,返回的代码如下:
There are 2 faces in the given image
请注意 returnFaceAttributes
这个请求体参数,它让你可以指定面部的多个属性,Face API 将根据这些属性分析给定的人脸。欲了解更多关于这些属性的信息,请查看文档:bit.ly/2J3j6nM
。
让我们以一种可展示的方式将从 API 获得的响应嵌入图像中。我们将显示检测到的人脸的可能性别和可能年龄。我们将使用 matplotlib
、PIL
和 io
库,并将在 Jupyter notebook 中进行以下代码段的工作。我们将首先导入这些库:
%matplotlib inline #Only for Jupyter Notebook
import matplotlib.pyplot as plt
from PIL import Image
from matplotlib import patches
from io import BytesIO
要在图像上显示 API 返回的叠加信息,我们使用以下方法:
- 存储 API 响应:
response = requests.get(image_url)
- 从响应内容创建图像:
image = Image.open(BytesIO(response.content))
- 创建一个空白的图形:
plt.figure(figsize=(8,8))
- 显示使用响应创建的图像:
ax = plt.imshow(image, alpha=0.6)
- 遍历前面部分指定的人脸并提取必要的信息:
for face in faces:
# Extract the information
fr = face["faceRectangle"]
fa = face["faceAttributes"]
origin = (fr["left"], fr["top"])
p = patches.Rectangle(origin, fr["width"], fr["height"], fill=False,
linewidth=2, color='b')
ax.axes.add_patch(p)
plt.text(origin[0], origin[1], "%s, %d"%(fa["gender"].capitalize(), fa["age"]),
fontsize=20, weight="bold", va="bottom")
# Turn off the axis
_ = plt.axis("off")
plt.show()
你应该有一张这样的图片:
鼓励你尝试 API 提供的不同参数。接下来,我们将研究 自然语言理解(NLU)API。
使用文本分析 API 和 Python 提取文本信息
无论是有意还是无意,我们都一定遇到过一些令人惊叹的自然语言处理应用案例。无论是自动更正、下一个单词的建议,还是语言翻译,这些应用案例都非常重要,不容忽视。在本节中,我们将使用文本分析 API(azure.microsoft.com/en-us/services/cognitive-services/text-analytics/
)从给定的文本中提取有意义的信息。
你可以通过之前提到的链接免费试用 API,看看它的强大功能。在以下示例中,我输入了短语 I want to attend NeurIPS someday and present a paper there
,Text Analytics API 从中提取了四个有意义的信息:
观察 API 如何优雅地提取出短语中的所有关键信息。
现在我们将看到如何使用 Python 进行编程操作。设置步骤与之前完全相同。只需访问 portal.azure.com/#create/Microsoft.CognitiveServicesTextAnalytics
,并按照那里的步骤操作。一旦获得了用于访问 Text Analytics API 的 API 密钥,就可以进入下一个子部分。别忘了记下相应的端点。该端点应以 eastus.api.cognitive.microsoft.com/text/analytics/v2.0
开头。这个 URL 单独使用时无法正常工作,它需要一个后缀指向需要调用的方法。
从 Python 代码中使用 Text Analytics API
本节将展示如何在您自己的 Python 代码中使用 Text Analytics API。以下是使用它的步骤:
- 我们将通过导入所需的库来开始本节内容:
import requests
import os
from pprint import pprint
- 然后,我们将从环境变量中加载 Text Analytics API 的 API 密钥:
api_key = os.environ['text_api_key']
- 现在,我们指定一些 URL 来存储 API 端点:
text_analytics_base_url = \
'https://eastus.api.cognitive.microsoft.com/text/analytics/v2.0'
language_api_url = text_analytics_base_url + "/languages"
sentiment_api_url = text_analytics_base_url + "/sentiment"
key_phrase_api_url = text_analytics_base_url + "/keyPhrases"
- 现在,我们通过提供 API 密钥来定义
headers
参数:
headers = {"Ocp-Apim-Subscription-Key": api_key}
- 我们还将定义 body 参数。在我的示例中,我将保持与之前在基于 GUI 的演示中展示的相同的短语:
documents = { 'documents': [
{ 'id': '1', 'text': 'I want to attend NeurIPS someday and present a paper there.' }
]}
- 我们现在可以调用 Text Analytics 的相应 API。首先让我们开始检测语言:
response = requests.post(language_api_url, headers=headers, json=documents)
language = response.json()
pprint(language)
我们根据此得到相应的响应,如下所示:
请注意,我已经突出显示了语言部分。现在,让我们继续进行情感分析:
response = requests.post(sentiment_api_url, headers=headers, json=documents)
sentiment = response.json()
pprint(sentiment)
显示的情感如下所示:
请注意,这里使用的短语既不包含正面情感也不包含负面情感,因此得出了这样的评分。现在我们将从给定的文本中提取关键短语:
response = requests.post(key_phrase_api_url, headers=headers, json=documents)
phrases = response.json()
print(phrases)
关键短语如下所示:
注意到端点根据任务的不同发生了变化。您可以在 bit.ly/2JjLRfi
了解更多关于我们在前面示例中使用的端点参数。
CNTK 简介
CNTK 是 Microsoft 提供的一个框架。该框架是 ONNX 格式计划的一部分,允许在不同的神经网络工具包框架之间轻松转换模型。该框架负责微软软件和平台上深度学习生产工作负载的很大一部分。该框架于 2016 年推出,并且是 TensorFlow、PyTorch 等流行框架的竞争者。该框架完全开源,您可以在 github.com/microsoft/CNTK
上找到它。
CNTK 驱动了企业服务,如 Cortana 和 Bing,以及广告服务,如 Skype 翻译、Microsoft Cognitive Services 等。已证明它在多个应用中比 TensorFlow 和 PyTorch 等竞争者运行得更快。
在本节中,我们将学习一些 CNTK 的基础知识,然后继续创建一个 Django 应用程序,将基于 CNTK 的模型迁移到网页上。
开始使用 CNTK
由于 CNTK 的语法简单,并且能够无需像 TensorFlow 中的会话(session)概念就能工作,因此它是最容易入门的框架之一。大多数学习者对 TensorFlow 中的会话概念感到困惑。让我们看看如何在本地机器或 Google Colaboratory 上设置 CNTK。
在本地机器上安装
CNTK 框架支持 64 位和 32 位架构的机器。然而,目前它仅支持 Python 版本最高到 3.6。您可以在 pypi.org/project/cntk/
验证最新的支持版本。此外,CNTK 目前不提供 macOS 的已编译二进制版本。
要安装该框架,您可以使用 pip
包管理器,或者通过 Anaconda 使用已编译的二进制文件进行安装。假设已设置 Python 环境,您可以使用以下命令在 Windows 和 Linux 上安装 CNTK:
- 如果没有 Anaconda,请使用以下命令安装 CPU 版本:
# For CPU version
pip install cntk
- 使用以下命令安装 GPU 支持版本:
# For the GPU enabled version
pip install cntk-gpu
- 在启用 Anaconda 的机器上,可以使用
pip
和以下命令安装 CNTK 框架:
pip install <url>
<url>
可从 CNTK 网站 tiny.cc/cntk
获取。
该命令将类似于以下内容:
pip install https://cntk.ai/PythonWheel/CPU-Only/cntk-2.6-cp35-cp35m-win_amd64.whl
我们现在可以开始在 Google Colaboratory 上进行安装了。
在 Google Colaboratory 上安装
CNTK 框架在 Google Colaboratory 平台上默认不可用,因此必须与其他必要的模块一起安装。要在 Google Colaboratory 运行时安装 CNTK,请在脚本顶部使用以下命令:
!apt-get install --no-install-recommends openmpi-bin libopenmpi-dev libopencv-dev python3-opencv python-opencv && ln -sf /usr/lib/x86_64-linux-gnu/libmpi_cxx.so /usr/lib/x86_64-linux-gnu/libmpi_cxx.so.1 && ln -sf /usr/lib/x86_64-linux-gnu/openmpi/lib/libmpi.so /usr/lib/x86_64-linux-gnu/openmpi/lib/libmpi.so.12 && ln -sf /usr/lib/x86_64-linux-gnu/libmpi.so /usr/lib/x86_64-linux-gnu/libmpi.so.12 && pip install cntk
请注意,前面的命令是单行命令。如果将其拆分成多行,您应该确保对命令进行必要的修改。
一旦前面的步骤成功执行,在该运行时中您将不再需要使用此命令。因此,未来运行程序时,可以注释掉该命令。
通常,通过 C
别名将 CNTK 导入 Python 项目。我们使用以下代码将库导入到项目中:
import cntk as C
我们可以使用以下命令检查安装的 CNTK 版本:
print(C.__version__)
将 CNTK 导入项目后,我们可以继续进行创建深度学习模型的前期要求。
创建一个 CNTK 神经网络模型
在本节中,我们将完成创建预测神经网络之前所需的步骤,然后我们将创建神经网络本身:
- 我们首先将必要的模块导入到项目中:
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
from sklearn.datasets import fetch_openml
import random
import cntk.tests.test_utils
from sklearn.preprocessing import OneHotEncoder
import cntk as C # if you have not done this before in the project
sklearn
模块的 fetch_openml()
方法帮助我们直接下载本示例中使用的数据集——MNIST 手写数字数据集。OneHotEncoder
方法用于标签的独热编码。
- 接下来,设置程序执行期间需要的几个常量:
num_samples = 60000
batch_size = 64
learning_rate = 0.1
我们将在 60,000 个样本上进行训练,初始学习率为0.1
。在训练过程中,这个学习率可以动态更新。
- 我们接下来需要创建一个生成随机小批量数据的训练方法:
class Batch_Reader(object):
def __init__(self, data , label):
self.data = data
self.label = label
self.num_sample = data.shape[0]
def next_batch(self, batch_size):
index = random.sample(range(self.num_sample), batch_size)
return self.data[index,:].astype(float),self.label[index,:].astype(float)
每次调用前述方法时,会生成与先前步骤中设置的大小相等的批量数据——例如,每个批次包含 64 个样本。这些样本会从数据集中随机抽取。
- 现在需要获取数据集;为此,我们使用以下代码行:
mnist = fetch_openml('mnist_784')
数据获取完成后,可以将其分为训练集和测试集,如下所示:
train_data = mnist.data[:num_samples,:]
train_label = mnist.target[:num_samples]
test_data = mnist.data[num_samples:,:]
test_label = mnist.target[num_samples:]
- 数据集中的标签在输入训练模型之前需要进行 one-hot 编码。为此,我们使用以下代码:
enc = OneHotEncoder()
enc.fit(train_label[:,None])
train_encoded = enc.transform(train_label[:,None]).toarray()
- 现在我们可以为训练批量生成器创建一个生成器对象,如下所示:
train_reader = Batch_Reader(train_data, train_encoded)
- 让我们快速地对
test
数据集执行上述步骤:
enc = OneHotEncoder()
enc.fit(test_label[:,None])
test_encoded = enc.transform(test_label[:,None]).toarray()
test_reader = Batch_Reader(test_data, test_encoded)
- 现在,让我们创建一个 CNTK 神经网络模型。我们首先定义一些常量:
dimensions = 784
classes = 10
hidden_layers = 3
hidden_layers_neurons = 400
我们将输入数据的维度定义为784
。回想一下我们在第三章《创建你的第一个深度学习 Web 应用》中使用的 MNIST 数据集。MNIST 数据集中的图像以单维数组格式存储,包含 28 x 28 个值,范围从0
到255
。这些图像属于 10 个不同的类别,对应阿拉伯数字系统中的每个数字。我们保留了 3 个隐藏层,每个隐藏层有 400 个神经元。
- 然后我们创建两个 CNTK 的
input
变量,用于在创建模型时使用。这是 CNTK 中最重要的概念之一。
input = C.input_variable(dimensions)
label = C.input_variable(classes)
在 CNTK 中,input
变量本质上是一个占位符,我们在模型训练、评估或测试时用来填充样本。从数据集中获取的输入形状必须与此步骤中声明的input
变量的维度完全匹配。这里需要特别提到的是,很多人将输入的维度与数据集的特征数混淆。一个具有N个特征和M个样本的数据集,其形状为(M, N),因此该数据集的维度仅为2
:
def create_model(features):
with C.layers.default_options(init = C.layers.glorot_uniform(), activation = C.ops.relu):
hidden_out = features
for _ in range(hidden_layers):
hidden_out = C.layers.Dense(hidden_layers_neurons)(hidden_out)
network_output = C.layers.Dense(classes, activation = None)(hidden_out)
return network_output
- 我们创建了
create_model()
方法,它以特征作为输入参数。
首先,为模型设置默认值,使用均匀分布来初始化权重和其他值。默认的激活函数设置为ReLU
。
第一层包含特征本身,最终层包含一个维度等于类别数的向量。中间的所有层包含一个完全连接的网络,拥有 3 个隐藏层,每个隐藏层有 400 个神经元,并使用 ReLU 激活:
model = create_model(input/255.0)
最后,我们使用前面的函数创建模型。通过255
进行除法运算,实现数据集的归一化,将图像数组中的值限制在0
和1
之间。
训练 CNTK 模型
模型创建完成后,我们现在可以进行模型训练并让其学习预测。为此,我们需要使用 CNTK 模型对象,并将数据集中的样本拟合到它。与此同时,我们还需要记录 loss
和其他评估指标。我们需要执行以下步骤来训练模型:
- 创建
loss
和分类误差的占位符:
loss = C.cross_entropy_with_softmax(model, label)
label_error = C.classification_error(model, label)
- 现在,我们可以为 CNTK 框架设置一个
trainer
对象,用于执行实际的训练:
lrs = C.learning_rate_schedule(learning_rate, C.UnitType.minibatch)
learner = C.sgd(model.parameters, lrs)
trainer = C.Trainer(model, (loss, label_error), [learner])
- 现在,让我们进行训练:
epochs = 10
num_iters = (num_samples * epochs) / batch_size
for i in range(int(num_iters)):
batch_data, batch_label = train_reader.next_batch(batch_size=batch_size)
arguments = {input: batch_data, label: batch_label}
trainer.train_minibatch(arguments=arguments)
if i % 1000 == 0:
training_loss = False
evalaluation_error = False
training_loss = trainer.previous_minibatch_loss_average
evalaluation_error = trainer.previous_minibatch_evaluation_average
print("{0}: , Loss: {1:.3f}, Error: {2:.2f}%".format(i, training_loss, evalaluation_error * 100))
我们将训练的 epoch 数设置为 10
,以便进行快速训练和评估。你可以将其设置为更高的值以提高训练的准确性;然而,这可能在某些情况下导致没有更好的训练效果或过拟合。在每次达到 1,000 次迭代时,我们都会显示到目前为止的损失和评估误差。这些值的整体趋势应该是下降的。
测试并保存 CNTK 模型
在继续使用 Django 框架将此项目转换为 Web 应用程序之前,让我们快速测试一下模型在此训练中的准确性。我们将执行以下操作,从模型中进行预测:
predicted_label_probs = model.eval({input: test_data})
这将为数据集中的每个标签创建一个 NumPy 概率数组。必须将其转换为索引并与测试数据的标签进行比较。我们按如下方式进行操作:
predictions = np.argmax(predicted_label_probs, axis=1)
actual = np.argmax(test_encoded, axis=1)
correct = np.sum(predictions == actual)
print(correct / len(actual))
我们发现预测准确率约为 98%。这是一个非常好的值,我们将继续保存模型并通过 Django 使用它。保存 CNTK 模型的方法如下:
model.save("cntk.model")
成功保存模型后,如果你使用了 Colaboratory 来构建模型,你需要将 model
文件下载到本地系统。接下来,我们可以继续将模型部署到基于 Django 的服务器上。
Django Web 开发简介
Django 是使用 Python 进行 Web 开发的最流行框架之一。该框架轻量、健壮,并且得到社区的积极维护,社区会迅速修补安全漏洞并添加新特性。本书中我们介绍了 Flask 框架,它本质上是一个用于 Python Web 开发的简单框架。然而,Django 提供了许多内建功能,实施了最先进的方法和实践。
Django 项目的初始结构如下所示:
这些文件是在使用 django-admin
工具创建新 Django 项目时自动生成的。顶级目录 mysite
表示 Django 项目的名称。每个 Django 项目包含多个应用程序。应用程序类似于软件开发中的模块概念。它们通常是完整项目中的独立部分,并通过项目目录中的 mysite
主应用程序组合在一起。每个项目内部可以有多个应用程序。
让我们学习如何开始使用 Django 并创建一个新项目!
入门 Django
使用 Django 之前的首要步骤是安装它。幸运的是,这个框架可以轻松地作为一个模块从 Python PIP 仓库安装。它也可以在 Conda 仓库中找到。要安装 Django,打开一个新的终端窗口并使用以下命令:
conda install django
或者,如果你更喜欢使用 PIP,可以使用以下命令:
pip install django
这将把 Django 模块安装到你的 Python 环境中。
要检查是否已成功安装,请在终端中使用以下命令:
python -m django --version
这应该会输出一个版本号,例如- 2.0.8
。如果没有,请检查你的 Django 安装。
创建一个新的 Django 项目
Django 提供了一个名为django-admin
的便捷工具,可以用来生成 Django 项目所需的模板代码。要创建一个名为cntkdemo
的新项目,请使用以下代码:
django-admin startproject cntkdemo
这将创建所有的模板文件和文件夹。但是,我们必须在项目中创建至少一个应用。通过终端将当前工作目录更改为cntkdemo
文件夹。使用以下命令在此项目中创建一个应用:
python manage.py startapp api
因此,我们创建了一个名为api
的文件夹,其中包含以下文件夹;所有文件都带有自动生成的占位符代码和文档:
现在我们可以继续进行初步 UI 的编码了。
设置首页模板
现在,让我们创建一个当访问/
路由时加载的网页。还记得我们在项目中创建的api
应用吗?为了简化起见,我们将把首页作为这个应用的一部分。虽然可以在mysite
应用的urls.py
文件中创建这个路由,但我们会为api
应用提供一个独立的路由处理文件。
让我们从设置首页模板的步骤开始:
- 在
api
文件夹中创建一个文件urls.py
。这个文件相对于项目目录的完整路径是mysite/api/urls.py
。在这个文件中,让我们使用以下代码添加/
路由:
from django.urls import path
from . import views
urlpatterns = [
path('', views.indexView), # This line handles the '/' route.
]
-
保存此文件。前面的代码本质上为
api
应用添加了一个新路径/
(注意,这不是项目的路径!)。它导入了api
应用中的所有视图(views.py
文件)。请注意,indexView
仍然不存在。我们将在下一步后创建这个视图。 -
api
应用没有链接到主项目应用。我们需要在mysite/mysite/urls.py
文件中添加以下几行,以便启用api
应用的路由处理:
from django.contrib import admin
from django.urls import path
from django.urls import include # -- Add this line!
urlpatterns = [
path('', include('api.urls')), # -- Add this line!
path('admin/', admin.site.urls),
]
第一行导入了一个工具,用于将特定于应用的路由设置包含到项目应用中。我们通过使用api.urls
字符串,将urls.py
文件包含到api
应用中。这会自动将字符串转换为代码行,试图找到并包含正确的文件。
- 在
api
应用目录中的views.py
文件中,添加以下几行代码:
from django.http import HttpResponse
from django.template import loader
HttpResponse
方法允许view
方法返回一个 HTML 响应。loader
类提供了从磁盘加载 HTML 模板的方法。
- 现在,我们创建
indexView
方法:
def indexView(request):
template = loader.get_template('api/index.html')
context = {}
return HttpResponse(template.render(context, request))
indexView
方法加载api/index.html
模板文件,并使用context
字典中提供的变量以及可用于模板的request
参数进行渲染。目前,我们传递一个空的上下文,因为我们没有任何值要发送到模板。但如前所述,之前定义的api/index.html
文件并不存在。
- 让我们创建一个用于存放模板的文件夹,并将其链接到项目设置中。为此,进入项目的根目录并创建一个名为
templates
的文件夹。我们需要让项目能够识别该文件夹作为模板的目录。为此,我们需要修改mysite/mysite/settings.py
文件中的TEMPLATES
设置:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # -- Add this line!
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
添加前述行后,项目将会在mysite/templates/
文件夹中查找模板。
-
创建
index.html
模板文件。请注意,我们在第 4 步中提到的模板文件路径存在于
api
目录中。在templates
目录下创建一个名为api
的文件夹。在这个文件夹中,创建index.html
文件,并添加以下代码:
{% load static %}
...
<div class="jumbotron">
<h3 class="jumbotronHeading">Draw here!</h3>
...
</div>
<div class="jumbotron">
<h3>Prediction Results</h3>
<p id="result"></p>
</div>
<div id="csrf">{% csrf_token %}</div>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src="img/{% static "/index.js" %}"></script>
...
我们在前面的代码块的末尾包含了一些必需的脚本,其中包括从后端获取 CSRF token 的脚本。
- 现在,让我们在前面的代码块中,向具有
jumbotron
类的div
元素中添加一个canvas
元素,我们将在该元素上绘制数字。我们还将添加一个滑动条,用于选择绘图笔的宽度,如下所示:
<div class="jumbotron">
<h3 class="jumbotronHeading">Draw here!</h3>
<div class="slidecontainer">
<input type="range" min="10" max="50" value="15" id="myRange">
<p>Value: <span id="sliderValue"></span></p>
</div>
<div class="canvasDiv">
<canvas id="canvas" width="350" height="350"></canvas>
<p style="text-align:center;">
<button class="btn btn-success" id="predict-btn" role="button">Predict</button>
<button class="btn btn-primary" id="clearButton" role="button">Clear</button>
</p>
</div>
</div>
template
文件还包含了两个静态文件——style.css
和 script.js
。我们将在接下来的部分中创建这些文件。目前我们还没有创建用于将数据发送到服务器并呈现收到的响应的脚本。
- 现在,我们将开始添加与后端 API 通信所需的 JavaScript 代码。首先,我们创建一个方法来检查是否需要 CSRF token 来与后端通信。这只是一个工具函数,与调用后端 API 无关,因为有时后端 API 会被设计为接受没有 CSRF token 的请求。我们创建这个函数,如下所示:
<script type="text/javascript">
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
- 然后,我们为
Predict
按钮创建一个click
处理程序。这个处理程序函数首先设置所需的正确头信息以便与后端 API 通信,然后将画布上的绘图转换为数据 URL 字符串:
$("#predict-btn").click(function() {
var csrftoken = $('input[name=csrfmiddlewaretoken]').val();
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$('#predict-btn').prop('disabled', true);
var canvasObj = document.getElementById("canvas");
var img = canvasObj.toDataURL();
// MORE CODE TO BE ADDED BELOW THIS LINE
// MORE CODE TO BE ADDED ABOVE THIS LINE
});
</script>
- 最后,我们将代码添加到
Predict
按钮的click
处理函数中,以便通过 Ajax 调用后端,将画布中的数据发送出去,如下所示:
$("#predict-btn").click(function() {
...
// MORE CODE TO BE ADDED BELOW THIS LINE
$.ajax({
type: "POST",
url: "/predict",
data: img,
success: function(data) {
console.log(data);
var tb = "<table class='table table-hover'><thead><tr><th>Item</th><th>Confidence</th></thead><tbody>";
var res = JSON.parse(data);
console.log(res);
$('#result').empty.append(res.data);
$('#predict-btn').prop('disabled', false);
}
});
// MORE CODE TO BE ADDED ABOVE THIS LINE
...
});
</script>
- 在我们创建静态文件之前,我们需要为它们创建一个文件夹并将其链接到项目中。这与我们创建
templates
文件夹的方式类似。首先,在项目目录中创建一个名为static
的文件夹,路径为mysite/static/
。然后,修改mysite/mysite/settings.py
文件中的STATIC
配置,如下所示:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), # -- Add this line!
]
现在,我们可以像在index.html
文件中一样,使用模板文件顶部的{% load static %}
指令将静态文件加载到项目模板中。
- 创建
style.css
和script.js
文件—由于这些文件与本书的上下文没有直接关系,你可以直接从tiny.cc/cntk-demo
下载它们。
请注意,如果没有script.js
文件,项目将无法运行。
我们已经为在index.html
模板文件中绘制的图像预测创建了设置。然而,/predict
路由尚未创建。接下来的部分我们将看到如何在 Django 中加载并使用 CNTK 模型。
使用 Django 项目中的 CNTK 进行预测
在这一部分中,我们将首先设置所需的路由、视图和 CNTK 模型的导入,以便与 Django 一起使用。然后,我们将从保存的文件中加载 CNTK 模型,并使用它进行预测。
设置预测路由和视图
回想一下我们如何在api
应用中创建/
路由及其对应的视图:
- 首先,将以下行添加到
mysite/api/urls.py
中:
urlpatterns = [
path('', views.indexView),
path('predict', views.predictView), # -- Add this line!
]
这创建了/predict
路由。然而,视图predictView
尚未创建。
- 将以下行添加到
api
应用的views.py
文件中:
from django.http import JsonResponse
def predictView(request):
# We will add more code below this line
# We will add more code above this line
return JsonResponse({"data": -1})
请注意前面代码行中的占位符。我们将在接下来的步骤中在这里添加更多内容。
导入必要的模块
现在,让我们加载所有必要的模块,以便按照以下步骤使用 CNTK 模型进行预测:
- 将以下导入语句添加到
api
应用的views.py
文件中:
import os
from django.conf import settings
- 我们需要前面的导入语句来从磁盘加载模型:
import cntk as C
from cntk.ops.functions import load_model
前面的代码行将 CNTK 模块导入 Django 项目。load_model
方法将帮助我们加载保存的 CNTK 模型文件。
以下模块用于处理进行预测时使用的图像:
from PIL import Image
import numpy as np
以下模块提供了处理 Base64 编码字符串的实用功能,这是index.html
页面在请求中发送画布数据的格式:
import re
import base64
import random
import string
其他库将在它们在接下来的部分中使用时进行解释。
使用 CNTK 模型进行加载和预测
现在,我们将通过以下步骤进一步编辑predictView
视图:
- 首先,使用以下代码将 Base64 编码的图像字符串数据读取到变量中:
def predictView(request):
# We will add more code below this line
post_data = request.POST.items()
pd = [p for p in post_data]
imgData = pd[1][0].replace(" ", "+")
imgData += "=" * ((4 - len(imgData) % 4) % 4)
Base64 解码后的字符串没有适当的填充,并且包含需要转换为+
的空格。前面代码块中的最后两行对字符串进行了相同的操作。
- 接下来,我们将把这个 Base64 编码的字符串转换为 PNG 图像,并使用以下代码保存到磁盘:
filename = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(32)])
convertImage(imgData, filename)
第一行创建了一个 32 字符长的随机字符串作为文件名。下一行调用了convertImage
方法,将base64
字符串存储为提供的文件名。
- 然而,
convertImage
方法尚未定义。在predictView
方法外部,按如下方式添加该函数的定义:
def convertImage(imgData, filename):
imgstr = re.search(r'base64,(.*)', str(imgData)).group(1)
img = base64.b64decode(imgstr)
with open(filename+'.png', 'wb') as output:
output.write(img)
该方法会从字符串中去除多余的元数据,然后解码该字符串并将其保存为 PNG 文件。
- 让我们回到
predictView
方法。我们将首先加载保存的image
文件:
image = Image.open(filename+'.png').convert('1')
我们还将把图像转换为仅黑白通道。这将图像中的通道数从 3 降至 1。
- 回想一下,MNIST 数据集中的所有图像的尺寸都是 28 x 28。我们必须将当前图像调整为相同的尺寸。我们通过以下代码来实现:
image.thumbnail((28,28), Image.ANTIALIAS)
- 现在,我们使用以下代码将图像转换为 NumPy 数组:
image_np = np.array(image.getdata()).astype(int)
image_np_expanded = np.expand_dims(image_np, axis = 0)
np.expanded_dims
是 NumPy 中的一个简单工具,用于为数组添加额外的维度,以便与大多数机器学习库兼容。
-
加载 CNTK 模型。首先,在项目的根目录创建一个名为
data
的文件夹,并将保存的model
文件复制到mysite/data/cntk.model
中。我们现在在
predictView
方法中加载 CNTK 模型,如下所示:
model = load_model(os.path.join(settings.BASE_DIR, "data/cntk.model"))
- 最后,我们可以预测图像的标签,如下所示:
predicted_label_probs = model.eval({model.arguments[0]: image_np_expanded})
data = np.argmax(predicted_label_probs, axis=1)
eval
方法的第一个参数期望传入图像的 NumPy 数组,并返回每个输出类别的概率列表。np.argmax
方法用于找到具有最高概率的类别的索引。
- 要返回输出,请修改
predictView
方法中的return
部分,如下所示:
# We will add more code above this line
return JsonResponse({"data": str(data[0])})
图像的预测标签作为包含在 data
变量中的数字发送,响应的 JSON 数据会在页面上显示。
测试网页应用程序
最后,我们可以测试我们开发的 CNTK + Django 应用程序。为此,打开终端并将其切换到项目的根目录。
使用以下命令启动 Django 服务器:
python manage.py runserver
如果端口空闲,服务器会在 localhost:8000
启动。打开网页浏览器,点击提供的画布上绘制数字并点击预测按钮。你可以在页面底部看到模型的预测结果,如下所示:
请注意,模型在前面的屏幕截图中返回了正确的输出,即 2。因此,我们得出结论,CNTK 模型通过 Django 部署已完成。
总结
在这一章,我们介绍了 Microsoft AI 和 Azure 云服务在网站上执行深度学习的相关功能。我们看到了如何使用面部 API 来预测图像中人物的性别和年龄,以及如何使用文本分析 API 来预测给定文本的语言、关键短语或任何句子的情感。最后,我们使用 CNTK 在 MNIST 数据集上创建了一个深度学习模型。我们展示了如何保存模型,并通过基于 Django 的 Web 应用程序将其作为 API 部署。通过 Django 部署保存的模型可以轻松适配其他深度学习框架,如 TensorFlow 或 PyTorch。
在下一章,我们将讨论一个通用框架,用于使用 Python 构建生产级深度学习应用程序。
第十二章:深度学习在生产中的应用(智能 Web 应用)
本节提供了不同的案例研究,展示了如何开发和部署深度学习 Web 应用(使用深度学习 API),并展示了如何使用深度学习措施来保护 Web 应用。
本节包括以下章节:
-
第九章,深度学习驱动的网站通用生产框架
-
第十章,使用深度学习保护 Web 应用
-
第十一章,DIY – 一个 Web DL 生产环境
-
第十二章,使用 DL API 和客户支持聊天机器人创建一个 E2E Web 应用
第十三章:深度学习驱动网站的一般生产框架
我们已经在前几章中覆盖了如何在我们的应用程序中使用行业级的云深度学习(DL)API,并通过实际示例学习了它们的使用。在这一章中,我们将涵盖开发深度学习驱动网站的总体框架。这将需要我们将迄今为止学到的所有内容结合起来,以便在实际案例中加以应用。在这一章中,我们将学习如何通过首先准备数据集来构建生产环境中的深度学习 Web 应用程序。接着我们将在 Python 中训练一个深度学习模型,然后使用 Flask 将深度学习模型包装成 API。
以下是本章的高层次总结:
-
定义我们的项目问题陈述
-
将问题分解为多个组件
-
构建一个心理模型来绑定项目组件
-
我们应该如何收集数据
-
遵循项目的目录结构
-
从零开始构建项目
技术要求
你可以在 github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter9
访问本章使用的代码。
要运行本章使用的代码,你需要以下软件:
-
Python 3.6+
-
Python PIL 库
-
NumPy
-
Pandas
-
自然语言工具包(NLTK)
-
Flask 1.1.0+ 及以下兼容版本:
-
FlaskForm
-
wtforms
-
flask_restful
-
flask_jsonpify
-
所有其他安装将在本章中介绍。
定义问题陈述
任何项目都应该从明确的问题陈述开始,否则项目开发注定会受到影响。问题陈述主导着项目开发管道中的所有主要步骤,从项目规划到项目成本。
在基于深度学习的 Web 项目中,问题陈述会引导我们如下:
-
确定我们需要什么样的数据。
-
在代码、规划和其他资源方面会有多少复杂性。
-
我们将开发什么样的用户界面。
-
人类的参与程度有多少,以便我们可以准备关于项目的人工资源等的估算。
因此,为了开始进一步的项目开发,确实需要一个明确的问题陈述。
想象一下,你是一个深度学习工程师,所在的公司计划建立一个推荐系统,根据用户提供的某些标准从产品列表中推荐产品。你的老板要求你基于此开发一个概念验证(PoC)。那么,我们该如何开始呢?如前所述,我们先从定义问题陈述开始。
向最终推荐系统提供输入的主要实体是用户。基于用户的偏好(我们暂时称这些输入特征为偏好),系统将提供一份最匹配他们偏好的产品清单。所以,简而言之,问题陈述可以写成如下:
给定一组输入特征(用户偏好),我们的任务是推荐一份产品清单。
既然我们已经有了明确的问题陈述,可以继续前进,接下来我们将建立下一步的计划。
构建项目的思维模型
看着问题陈述,你可能会忍不住打开浏览器,开始寻找一些数据集。但是,正确地开发一个项目需要明确的规划,将其一步步地结构化。一个没有结构的项目不过是无舵的船。因此,我们从一开始就要对此保持谨慎。我们将讨论在我们的项目中扮演至关重要角色的模块。这还包括一些思维上的考虑。我喜欢将这一阶段称为构建项目的思维模型。
让我们花些时间进一步讨论问题陈述,以便弄清楚我们需要开发的核心模块。
我们的项目涉及根据用户的偏好向其推荐产品。因此,为了进行推荐,我们需要一个系统,能够理解用户提供的偏好集。为了理解这些偏好,系统需要进行某种类型的训练,我们将在其中实施深度学习(DL)。但是,偏好是什么?它们会是什么样的?你将在现实世界的项目中经常遇到这些问题,通常这些问题需要人类参与其中。
现在,花点时间思考一下,在选择购买产品时你通常会关注哪些方面。让我们列出这些内容:
-
这个产品的规格是什么?如果我想要一件大号 T 恤,就不应该推荐我一件小号 T 恤。
-
这个产品的价格是多少?用户的钱有限,这个推荐对他们的钱包是否合适?
-
这个产品是什么品牌?用户通常会有对几家制造商生产的类似产品的品牌偏好。
请注意,前面的这些要点并没有特定的顺序。
那么,从前一部分开始,我们已经开始有了一个大致的了解,我们需要一个界面(在我们的案例中,本质上是一个网页),让用户提供他们的偏好。根据这些偏好,我们的系统将预测一组它认为最合适的产品。这就是深度学习(DL)部分的作用所在。正如我们从前面的章节回顾到的那样,深度学习模型要在给定问题上发挥作用,需要通过一些能够尽可能贴近问题的数据进行训练。现在,让我们讨论一下系统的数据部分。
我们有一个现成的项目数据集——由亚马逊提供、斯坦福网络分析项目团队创建的亚马逊美食评论数据集。虽然该数据集的大小很大,但在本章创建示例时,我们不会使用完整的数据集。此时可能会产生一个直接的问题,那就是数据集的样子如何?我们需要制定一个大致的计划,以决定以下内容:
-
我们将选择哪些特征来构建数据集
-
我们将从哪里收集数据
在继续讨论之前,我们对原问题陈述做一点增强。以下是原问题陈述:
给定一组输入特征(用户偏好),我们的任务是建议一份产品清单。
如果我们的系统推荐低质量产品,用户会不喜欢它。因此,我们将稍微修改问题陈述,如下所示:
给定一组输入特征(用户偏好),我们的任务是建议一份最佳购买产品的清单。
为了让我们的系统根据给定标准推荐最佳产品列表,首先需要知道产品的平均评分。除了产品名称之外,关于某个特定产品,以下信息也是有用的:
-
规格
-
产品类别
-
卖家名称
-
平均价格
-
预期交货时间
在准备数据时,我们会查找有关特定产品的前述指示。接下来是一个问题,我们将从哪里收集这些数据?答案是亚马逊!亚马逊以其在电子商务行业的服务而闻名,提供各种产品及其相关信息,如评分、产品规格、商品价格等。但是假设亚马逊不允许你直接下载这些数据作为压缩文件。为了以所需的形式从亚马逊获取数据,我们将不得不使用网页抓取技术。
到目前为止,在讨论中,我们已经确定了项目的两个大致方向:
-
一个接收用户偏好的接口
-
代表我们所处理问题的相关数据
对于深度学习建模,我们将从简单的全连接神经网络架构开始。通常从一个简单的模型开始并逐步增加复杂度是很有用的,因为这样可以使代码库更容易调试。
所以,从这一点来看,我们可以比较确定地说,以下三个模块将在本项目中发挥重要作用:
-
一个接口
-
数据
-
一个深度学习模型
希望到目前为止,你已经对如何着手开发项目有了一个大致的了解。你此时应该提出哪些问题,以及你可能需要考虑的事项,可以从你现在掌握的框架中得出。
我们不希望我们的推荐系统对任何事物产生偏见。数据中可能隐藏着许多类型的偏见,而这些偏见很自然地会导致使用这些数据的深度学习系统继承这些偏见。
若想了解更多关于机器学习系统中不同类型的偏见,建议参考这篇文章:developers.google.com/machine-learning/crash-course/fairness/types-of-bias
。在我们的例子中,一个惊人的偏见实例是,男性访客得到的产品推荐仅仅基于其性别,而不是根据任何其他访客浏览模式。这可能是错误的,也可能是无意中产生的,但类似情况会使我们的模型变得非常不合适。在接下来的部分,我们将讨论如何避免数据偏见的一些方法。
避免在一开始就获取错误数据的机会
什么是错误数据?我们是不是只在谈论那些有错误值的数据?答案是否定的。除了数据有错误值或缺失值外,错误数据还可能包含一些微妙但严重的错误,这些错误可能导致模型训练效果差,甚至产生偏见。因此,识别这些错误数据并在训练模型之前将其去除是非常重要的。识别这些错误的主要方法有五种:
-
查找缺失值。
-
查找那些看起来不合常理或不可能的值——换句话说,异常值。
-
不要在数据集中包括可能导致数据泄露的任何特征。
-
确保数据集中所有类别的评估样本数量大致相同。
-
确保你解决问题的设计本身不会引入偏见。
一旦我们明确了这些要点,就可以进入数据收集过程中需要特别注意的更具体领域。在数据收集过程中,制定一个适当的计划,牢记数据源的所有特性和问题要求是非常重要的。
假设你在爬取亚马逊美国站的产品数据时,错误地访问了亚马逊印度站。此时,爬虫可能会提供来自印度站的数据,这些数据可能不适合向美国居民推荐。
此外,由于亚马逊及类似的服务(如 Flipkart)借助推荐系统来为客户推荐最合适的产品,因此在数据收集过程中,爬虫不应受这些推荐的影响。爬虫应定期清除其上下文,以避免由于亚马逊实施的 AI 系统而产生偏见的结果。
让我们以亚马逊美食评论数据集为例。尽管从表面上看,这个数据集似乎非常平衡,但我们可以揭示出其中的很多偏差。考虑一下顾客为产品撰写评论时的文字长度。让我们将评论的长度与评分绘制成图表。以下图表展示了 1 星和 2 星评分的产品分布:
以下图表展示了 3 星和 4 星评分的产品分布:
以下图表展示了 5 星评分的产品分布:
注意到更多的正面评价中包含更多的文字。这会直接导致数据集中的大多数词汇,都与较高的评分相关联。现在,设想一个场景,其中用户写了一个冗长的负面评论,给出了较低的评分。由于我们的模型训练时将较长的评论与正面评分相关联,它可能会将这个负面评论误判为正面评价。
关键问题在于,现实世界的数据可能包含许多边缘案例,如图所示,如果这些边缘情况没有得到妥善处理,模型很可能会出错。
如何构建一个不理想的 AI 后端
考虑到 Web 应用程序可能发展到的庞大规模,以及几乎所有其他平台都强烈依赖于作为 Web 服务运行的后端,因此,后端的设计和执行至关重要。即使是处于概念验证阶段的基于 AI 的应用程序,通常在响应时也不会非常快速,或者需要大量时间来训练新样本。
虽然我们将讨论一些技巧和窍门,以帮助构建一个不会因瓶颈而在压力下崩溃的后端,但在开发一个集成了 AI 的后端时,我们需要先确定一些在开发过程中应该尽量避免的问题。
期望网站的 AI 部分实现实时响应
AI 计算代价高昂,显而易见,这对于一个旨在尽快为客户提供服务的网站来说是不可取的。虽然使用更小的模型或浏览器内的 AI(例如 TensorFlow.js 或其他库)可以提供实时 AI 响应的体验,但它们在客户端处于慢速网络环境或使用低端设备时,依然会遇到问题。因此,无论是浏览器内的 AI 模型还是快速响应的轻量级 AI 模型,都受设备配置和网络带宽的限制。因此,网站的后端应该理想上与处理 AI 模型响应的部分分开,这样它就能快速响应客户端。两者需要并行工作,共享一个数据存储,并保持合适的交互方式,使得负责响应客户端的后端代码不依赖于 AI 模型部分。
假设来自网站的数据是理想的
尽管与项目对应的网站或应用可能看起来是理想的数据收集方法,但来自它的数据不能假设没有错误。网络请求失败、恶意连接,或者用户提供的垃圾输入都可能导致数据不适合训练。一个非恶意用户可能会因为网络问题而在短时间内刷新同一页面 10 到 20 次,这不应该增加该页面的查看重要性。所有从网站收集的数据必须根据模型的要求进行清理和过滤。必须牢记,网站面临的挑战几乎肯定会影响收集数据的质量。
一个示例的端到端 AI 集成网络应用
现在我们已经讨论了创建 AI 驱动网站后台时的概览和避免的陷阱,让我们开始构建一个——尽管是一个相对简单的——展示解决方案整体概览的系统。
我们将涵盖以下步骤,如前所述:
-
根据问题陈述收集数据
-
数据清理和预处理
-
构建 AI 模型
-
创建界面
-
在界面上使用 AI 模型
虽然我们之前已经讨论了收集数据时的陷阱,但我们在这里简要讨论可以用来完成任务的工具和方法。
数据收集与清理
从一般角度来看,收集数据的目的可以有多个数据源。您可以从网站抓取数据,或简单地下载一些准备好的数据集。还可以采用其他方法,例如以下几种:
-
在应用程序/网站的运行时生成数据
-
从应用程序或智能设备进行日志记录
-
通过系统化的表单(例如测验或调查)直接收集用户数据
-
从调查机构收集数据
-
通过特定方法(科学数据)和其他方式测量的观测数据
beautifulsoup
是一个常用的网页抓取库。Scrapy
是另一个流行的工具,并且可以非常迅速地使用。
数据清理完全取决于您收集的数据形式,这在本书的前几章中已有讨论。我们将假设您能够将数据转换为适合您想要继续进行模型构建部分的格式。在本节的后续主题中,我们将使用一个名为 Amazon Fine Food Reviews 的准备好数据集,您可以从 www.kaggle.com/snap/amazon-fine-food-reviews.
下载该数据集。解压下载的 ZIP 文件后,您会得到一个名为 Reviews.csv
的数据文件。
观察如何进行网页抓取并准备清理数据集的一个良好起点是 github.com/Nilabhra/kolkata_nlp_workshop_2019
。
构建 AI 模型
现在,我们将准备 AI 模型,该模型将根据用户的查询推荐产品。为此,让我们创建一个新的 Jupyter Notebook。
进行必要的导入
我们首先导入所需的 Python 模块到项目中:
import numpy as np
import pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import WordPunctTokenizer
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
# Comment below line if you already have stopwords installed
nltk.download('stopwords')
我们导入TfidfVectorizer
,帮助我们创建词频-逆文档频率(TF-IDF)向量,以进行自然语言处理。TF-IDF 是衡量一个词在单一文档中重要性的数值指标,考虑到一组可能包含或不包含该词的文档。数值上,它增加了当一个词在单一文档中频繁出现而在其他文档中不常出现时的权重。TF-IDF 非常流行,目前全球超过 80%的基于自然语言的推荐系统都在使用它。
我们还导入了WordPunctTokenizer
。分词器的作用是将文本拆分为基本的词元。例如,一个大段落可以被拆分为句子,进一步拆分为单词。
读取数据集并准备清理函数
我们将使用ISO-8859-1
编码读取 Amazon Fine Food Reviews 数据集。这样做是为了确保我们不会丢失评论文本中使用的任何特殊符号:
df = pd.read_csv('Reviews.csv', encoding = "ISO-8859-1")
df = df.head(10000)
由于数据集非常庞大,我们在本章中将工作范围限制为数据集中的前 10,000 行。
我们需要从文本中去除停用词,并过滤掉如括号等不属于自然书面文本的符号。我们将创建一个名为cleanText()
的函数,执行过滤和去除停用词的操作:
import string
import re
stopwordSet = set(stopwords.words("english"))
def cleanText(line):
global stopwordSet
line = line.translate(string.punctuation)
line = line.lower().split()
line = [word for word in line if not word in stopwordSet and len(word) >= 3]
line = " ".join(line)
return re.sub(r"[^A-Za-z0-9^,!.\/'+-=]", " ", line)
使用前面的函数,我们已经从文本中去除了停用词和任何少于三个字符的词。我们已经过滤掉了标点符号,并且只保留了文本中相关的字符。
切片出所需的数据
数据集包含的数据比我们当前演示所需的更多。我们将提取ProductId
、UserId
、Score
和Text
列,以准备我们的演示。产品名称出于隐私原因进行了加密,就像用户名称也被加密一样:
data = df[['ProductId', 'UserId', 'Score', 'Text']]
在数据科学中,保持数据加密并且不包含个人信息是一个挑战。必须从数据集中删除可能使其识别出私密实体的部分。例如,您需要从评论文本中删除人名和组织名,以防止即使已经加密了产品和用户 ID,产品和用户仍然能被识别出来。
应用文本清理
我们现在将应用文本过滤和停用词移除函数来清理数据集中的文本:
%%time
data['Text'] = data['Text'].apply(cleanText)
显示任务所需的时间。
请注意,前面的代码块仅适用于 Jupyter Notebook,而不能在普通的 Python 脚本中运行。要在普通 Python 脚本中运行,请移除%%time
命令。
将数据集拆分为训练集和测试集
由于我们有一个单一的数据集,我们将其分成两部分,特征和标签部分分开:
X_train, X_valid, y_train, y_valid = train_test_split(data['Text'], df['ProductId'], test_size = 0.2)
我们将使用 sklearn
模块中的 train_test_split()
方法,将数据集分为 80% 用于训练,20% 用于测试。
聚合关于产品和用户的文本
我们现在将按用户和产品 ID 聚合数据集中的评论。我们需要每个产品的评论,以确定该产品适合什么样的用户:
user_df = data[['UserId','Text']]
product_df = data[['ProductId', 'Text']]
user_df = user_df.groupby('UserId').agg({'Text': ' '.join})
product_df = product_df.groupby('ProductId').agg({'Text': ' '.join})
类似地,通过用户聚合的评论将帮助我们确定用户喜欢什么。
创建用户和产品的 TF-IDF 向量化器
我们现在将创建两个不同的向量化器,一个是用于用户,另一个是用于产品。我们需要这些向量化器来确定用户需求与产品评论之间的相似性。首先,我们将为用户创建向量化器并显示其形状:
user_vectorizer = TfidfVectorizer(tokenizer = WordPunctTokenizer().tokenize, max_features=1000)
user_vectors = user_vectorizer.fit_transform(user_df['Text'])
user_vectors.shape
然后,我们将为产品创建向量化器:
product_vectorizer = TfidfVectorizer(tokenizer = WordPunctTokenizer().tokenize, max_features=1000)
product_vectors = product_vectorizer.fit_transform(product_df['Text'])
product_vectors.shape
我们使用 WordPunctTokenizer
来分解文本,并使用 TfidfVectorizer
对象的 fit_transform
方法来准备向量,这些向量将单词字典映射到它们在文档中的重要性。
按评分创建用户和产品的索引
我们使用 pandas
模块的 pivot_table
方法,创建用户对产品的评分矩阵。我们将使用这个矩阵进行矩阵分解,以确定用户喜欢的产品:
userRatings = pd.pivot_table(data, values='Score', index=['UserId'], columns=['ProductId'])
userRatings.shape
我们还将把用户和产品的 TfidfVectorizer
向量转换成适用于矩阵分解的矩阵:
P = pd.DataFrame(user_vectors.toarray(), index=user_df.index, columns=user_vectorizer.get_feature_names())
Q = pd.DataFrame(product_vectors.toarray(), index=product_df.index, columns=product_vectorizer.get_feature_names())
我们现在可以创建矩阵分解函数。
创建矩阵分解函数
我们现在将创建一个函数来执行矩阵分解。矩阵分解在 2006 年的 Netflix 奖挑战赛中成为推荐系统算法的热门方法。它是一类算法,将用户-项目矩阵分解成两个较低维度的矩阵,这两个矩阵可以相乘以恢复原始的高阶矩阵:
def matrix_factorization(R, P, Q, steps=1, gamma=0.001,lamda=0.02):
for step in range(steps):
for i in R.index:
for j in R.columns:
if R.loc[i,j]>0:
eij=R.loc[i,j]-np.dot(P.loc[i],Q.loc[j])
P.loc[i]=P.loc[i]+gamma*(eij*Q.loc[j]-lamda*P.loc[i])
Q.loc[j]=Q.loc[j]+gamma*(eij*P.loc[i]-lamda*Q.loc[j])
e=0
for i in R.index:
for j in R.columns:
if R.loc[i,j]>0:
e= e + pow(R.loc[i,j]-np.dot(P.loc[i],Q.loc[j]),2)+lamda*(pow(np.linalg.norm(P.loc[i]),2)+pow(np.linalg.norm(Q.loc[j]),2))
if e<0.001:
break
return P,Q
然后,我们执行矩阵分解并记录所花费的时间:
%%time
P, Q = matrix_factorization(userRatings, P, Q, steps=1, gamma=0.001,lamda=0.02)
完成后,我们需要保存模型。
将模型保存为 pickle 文件
现在,在项目的 root
目录下创建一个名为 api
的文件夹。然后,保存经过训练的模型,即在用户-产品评分矩阵分解后得到的低阶矩阵:
import pickle
output = open('api/model.pkl', 'wb')
pickle.dump(P,output)
pickle.dump(Q,output)
pickle.dump(user_vectorizer,output)
output.close()
将模型保存为二进制的 pickle 文件,使我们可以在模型部署到网站后端时快速将其加载到内存中。
现在我们已经完成了预测模型的开发,我们将继续构建一个应用程序界面来使其能够运行。
构建接口
为了构建 Web 应用的接口,我们需要考虑如何让用户与系统交互。在我们的案例中,我们希望用户提交搜索查询时,系统会根据他们搜索的内容在搜索框中即时展示建议。这意味着我们需要系统能够实时响应并即时生成建议。为了构建该系统,我们将创建一个 API 来响应搜索查询。
创建一个 API 来回答搜索查询
我们将创建一个接受 HTTP 请求形式查询的 API,并根据用户输入的搜索查询回复产品建议。为此,请遵循以下步骤:
- 我们将从导入 API 所需的模块开始。我们在前一节中讨论了这些导入的模块:
import numpy as np
import pandas as pd
from nltk.corpus import stopwords
from nltk.tokenize import WordPunctTokenizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer
from flask import Flask, request, render_template, make_response
from flask_wtf import FlaskForm
from wtforms import StringField, validators
import io
from flask_restful import Resource, Api
import string
import re
import pickle
from flask_jsonpify import jsonpify
- 我们还将导入
Flask
模块,创建一个快速的 HTTP 服务器,可以在定义的路由上以 API 的形式提供服务。我们将按如下所示实例化Flask
应用对象:
DEBUG = True
app = Flask(__name__)
app.config['SECRET_KEY'] = 'abcdefgh'
api = Api(app)
应用配置中的SECRET_KEY
值由你自行决定。
- 然后,我们将创建一个
class
函数来处理我们收到的以搜索查询形式的文本输入:
class TextFieldForm(FlaskForm):
text = StringField('Document Content', validators=[validators.data_required()])
- 为了封装 API 方法,我们将它们包装在
Flask_Work
类中:
class Flask_Work(Resource):
def __init__(self):
self.stopwordSet = set(stopwords.words("english"))
pass
- 我们在模型创建过程中使用的
cleanText()
方法再次被需要。它将用于清理并过滤用户输入的搜索查询:
def cleanText(self, line):
line = line.translate(string.punctuation)
line = line.lower().split()
line = [word for word in line if not word in self.stopwordSet and len(word) >= 3]
line = " ".join(line)
return re.sub(r"[^A-Za-z0-9^,!.\/'+-=]", " ", line)
- 我们为应用程序定义了一个主页,该主页将从稍后在模板中创建的
index.html
文件加载:
def get(self):
headers = {'Content-Type': 'text/html'}
return make_response(render_template('index.html'), 200, headers)
- 我们创建了基于
post
方法的预测路由,在接收到用户的搜索查询后,将返回产品建议:
def post(self):
f = open('model.pkl', 'rb')
P, Q, userid_vectorizer = pickle.load(f), pickle.load(f), pickle.load(f)
sentence = request.form['search']
test_data = pd.DataFrame([sentence], columns=['Text'])
test_data['Text'] = test_data['Text'].apply(self.cleanText)
test_vectors = userid_vectorizer.transform(test_data['Text'])
test_v_df = pd.DataFrame(test_vectors.toarray(), index=test_data.index,
columns=userid_vectorizer.get_feature_names())
predicted_ratings = pd.DataFrame(np.dot(test_v_df.loc[0], Q.T), index=Q.index, columns=['Rating'])
predictions = pd.DataFrame.sort_values(predicted_ratings, ['Rating'], ascending=[0])[:10]
JSONP_data = jsonpify(predictions.to_json())
return JSONP_data
- 我们将
Flask_Work
类附加到Flask
服务器。这完成了运行时的脚本。我们已经设置了一个 API,根据用户的搜索查询建议产品:
api.add_resource(Flask_Work, '/')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=4000, debug=True)
将此文件保存为main.py
。创建好 API 脚本后,我们需要托管服务器。
- 在本地计算机上执行此操作,请在终端中运行以下命令:
python main.py
这将在计算机上启动服务器,并绑定到端口4000
,如所示:
然而,我们仍然需要准备一个用户界面来使用这个 API。我们将在接下来的部分进行。
创建用于使用 API 的接口
现在,我们将创建一个简单、简约的 UI 来使用我们创建的 API。实际上,我们将创建一个单一的搜索框,用户在其中输入他们想要的产品或产品规格,API 则根据用户的查询返回推荐。我们不会讨论构建 UI 的代码,但我们已经将其包含在 GitHub 存储库中,可以通过tiny.cc/DL4WebCh9
找到。
一旦启动服务器,该 UI 将在http://127.0.0.1:4000
上可见,如创建 API 以回答搜索查询部分的第 9 步所示。
我们创建的界面如下所示:
用户输入搜索查询并获得推荐,如下所示:
我们的应用程序没有保存用户会话的好处。同样,它没有用户预期预算的参数,这通常是决定产品是否适合用户的重要因素。将这些功能添加到 Web 应用程序并利用它们的好处是很容易的。
总结
作为一般概述,利用 DL 强大功能的 Web 应用程序有几种通过 API、浏览器内 JavaScript 或通过在应用程序后端悄悄嵌入 DL 模型的设置方法。在本章中,我们看到如何使用其中最常见的方法——基于 API 的 DL Web 应用程序——同时,我们还大致概述了如何设计类似解决方案。我们涵盖了问题陈述的思考过程以及随后的解决方案,以及在设计集成 DL 模型的 Web 应用程序时要避免的缺陷和痛点。
在下一章中,我们将涵盖一个端到端项目,该项目将 DL 集成到 Web 应用程序中,以用于安全目的。我们将看到 DL 如何帮助我们识别可疑活动并阻止垃圾用户。
第十四章:使用深度学习保障 Web 应用的安全
安全性对任何网站而言都是至关重要的——同样对所有软件也是如此。如今,随着可用计算能力的增加以及技术领域的发展,安全威胁正在不断演变。因此,网站需要采用最好的安全措施来保护数据和用户信息的安全。具有在线商业活动的网站通常面临较高的风险,并且很常见它们会遭遇前所未见的安全攻击。新的攻击特别难以被基于规则的安全系统识别和阻止,因此,你可以考虑使用深度学习驱动的安全系统,这些系统能够有效替代基于规则的系统,并且能够正确识别和阻挡新的威胁。
本章讨论了你可以使用深度学习与 Python 来保障网站安全的几种技巧和技术。我们将介绍 reCAPTCHA 和 Cloudflare,并讨论它们如何用于增强网站的安全性。我们还将向你展示如何通过基于深度学习的技术和 Python 后端实现安全机制,以检测网站上的恶意用户。本章将涵盖以下主题:
-
reCAPTCHA 的故事
-
DIY——在 Django 中检测恶意用户
-
在 Web 应用中使用 Python 的 reCAPTCHA
-
使用 Cloudflare 提升网站安全
本章的讨论将从 reCAPTCHA 的故事开始——这是一个由 Google 创建的巧妙工具,它改变了互联网。
技术要求
你可以通过以下链接访问本章的代码:github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter10
。
你需要以下软件来运行本章的代码:
-
Python 3.6+
-
TensorFlow 1.14
-
Keras 兼容 TensorFlow 1.14
-
Django 2.x
reCAPTCHA 的故事
对人类友好,对机器人难——这是 reCAPTCHA 的标语,表达了 reCAPTCHA 的基本理念:它是一个用来判定应用或网站上的用户是否为真实人类用户还是自动脚本的系统。reCAPTCHA 是验证码技术的具体实现,采用扭曲、弯曲的字母和数字图像,挑战用户去解读并将其以简单的文本形式输入。
如果你是 2000 年代初的普通互联网用户,你可能在许多网站上看到过类似于以下的验证码(CAPTCHA)图片:
验证码(CAPTCHA)是完全自动化的公共图灵测试,用以区分计算机和人类的缩写。
CAPTCHA 系统由雅虎推广,迅速被数百万个网站采纳。然而,尽管这个系统为网站提供了安全性保障,它却耗时且经常被恶意程序员攻破。人们时不时会创建新的 CAPTCHA 系统,设计和视觉元素的组合各不相同。
与此同时,开发者们正在解决另一个完全不同的问题——数字化印刷书籍和其他文本。一个快速的解决方案是扫描书籍;即使用光学字符识别(OCR)将书籍转换成初步的数字文本形式。对于使用标准字体且扫描质量良好的印刷内容,这些转换效果良好。然而,对于印刷不规范的书籍和手稿,转换的准确性则大打折扣。人们越来越多地将图像上传到在线平台,试图从这些图像中提取文本并用于多种目的,如确定图像中的内容、地点或品牌。
CAPTCHA 的起源存在争议,多个小组声称自己是发明者,但正是在 2003 年,路易斯·冯·安创造了“CAPTCHA”这一术语,并随后成为了 reCAPTCHA 的创始人,该项目后来被谷歌收购。
群体外包的先驱路易斯·冯·安(Luis von Ahn)使用 reCAPTCHA 程序来展示从印刷书籍扫描中裁剪出来的极小文本块。只有人类才能轻松解决这些挑战,而自动化程序会失败。与此同时,这些书籍正通过大量人类用户的贡献被缓慢地数字化,这是一种未知的群体外包活动。reCAPTCHA 对用户而言仍然是一个麻烦,但书籍数字化的问题得以解决。
随着时间的推移,reCAPTCHA 演变为使用基于 AI 的系统来识别真实用户和假用户。在写这本书时,reCAPTCHA 仍在谷歌的积极开发下,目前已经进入第三个版本,它允许在网页的后台进行隐形验证,仅当用户无法成功验证时,才会显示挑战。这为真正的用户节省了大量时间,并对机器构成了挑战。
我们现在将构建一个网站,使用基于深度学习的模型和 reCAPTCHA 提供安全元素。
恶意用户检测
网站上的恶意用户是指任何试图执行未授权任务的用户。在当今世界,恶意用户带来的威胁正在呈指数增长,多个全球科技巨头、政府机构和其他私营公司巨大的个人信息数据库被黑客公开曝光。必须建立能够自动缓解这些恶意攻击的系统。
为了识别我们样本网站中的恶意用户,我们创建了一个模型,它能够学习用户的正常行为,并在用户的行为与过去的使用模式发生显著变化时发出警报。
异常检测是机器学习的一个热门分支。它是一组算法,用于检测给定数据集中的数据样本,这些样本与大多数数据样本的属性不一致。比如,在一个狗狗收容所中检测到一只猫就是异常检测。异常检测可以通过多种方式进行:
-
通过使用列的最小最大范围
-
通过在数据的图表中发现突发的异常点
-
当数据在高斯曲线下绘制时,将位于极端位置的点标记为异常(异常点)
支持向量机、k 近邻和贝叶斯网络是一些最常用的异常检测算法。
我们如何定义用户在网站上的正常行为呢?
假设你使用一个网站,通常通过笔记本电脑登录。一般来说,你最多只需要两次尝试就能成功登录该网站。如果有一天你突然开始使用一台新笔记本电脑,那么登录过程就会显得可疑,可能是恶意尝试黑客入侵你的账户。如果新设备的位置是你最近没有去过的地方,或者是你从未去过的地方,那么就更加可疑。如果你尝试登录账户的次数达到 10 次,那么这种行为也会显得异常。没有任何可疑使用状态的情况是用户在网站上的正常行为。
有时,异常可能不是由于某个特定用户的不正常行为引起的。由于服务器的变化,用户的常规流量以及他们的行为可能会发生变化。我们必须小心,避免在这种情况下将所有用户都标记为恶意用户。此外,用户的不正常行为可能是由于其他原因引起的,而不仅仅是黑客攻击他们的账户。如果一个真实的用户突然开始访问他们本不该访问的网站部分,这也是一种异常,需要加以防止。
在我们的示例网站中,我们将集成这样的系统。为此,我们将在网站的登录页面上进行检查,尝试确定用户的登录行为是正常的还是异常的。我们将考虑用户从哪个页面登录,因为一个网站可能有多个登录页面,并且尝试确定该页面是否是用户常用的登录页面。如果用户尝试从一个他们通常不登录的页面登录,我们将把它标记为异常。这只是一个简单的标准,用于检查异常用户,实际上还有数百个其他参数。
基于 LSTM 的用户身份验证模型
我们将把这一部分分为两个主要子部分:
-
构建安全检查模型
-
将模型托管为 API
让我们从第一部分开始。
构建身份验证有效性检查模型
要根据用户的登录活动对其进行身份验证,我们需要一个检查请求的 API。我们可以按以下步骤构建此模型:
- 让我们从开发身份验证模型开始,该模型确定用户是否未按常规方式操作。我们在运行 Python 3.6+的 Jupyter 笔记本中导入所需的模块,如下所示:
import sys
import os
import json
import pandas
import numpy
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout
from keras.layers.embeddings import Embedding
from keras.preprocessing import sequence
from keras.preprocessing.text import Tokenizer
from collections import OrderedDict
- 现在我们可以将数据导入到项目中。我们将使用以下数据集:
github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/blob/master/Chapter10/model/data/data-full.csv
。如下图所示,我们将数据集加载到项目中:
file = 'data-full.csv'
df = pandas.read_csv(file, quotechar='|', header=None)
df_count = df.groupby([1]).count()
total_req = df_count[0][0] + df_count[0][1]
num_malicious = df_count[0][1]
print("Malicious request logs in dataset: {:0.2f}%".format(float(num_malicious) / total_req * 100))
您将看到关于数据的一些一般统计信息,如下所示:
你会发现数据包含文本,如下所示:
这一观察很重要,我们将在后续步骤中参考此屏幕截图。
- 然而,所有的数据都是字符串格式。我们需要将其转换为适当类型的值。此外,数据集目前仅包含一个 DataFrame;我们将使用以下代码将其拆分为两部分——训练列和标签列:
df_values = df.sample(frac=1).values
X = df_values[:,0]
Y = df_values[:,1]
- 另外,我们需要删除一些列,因为我们只想使用数据集中与我们任务相关的特征:
for index, item in enumerate(X):
req = json.loads(item, object_pairs_hook=OrderedDict)
del req['timestamp']
del req['headers']
del req['source']
del req['route']
del req['responsePayload']
X[index] = json.dumps(req, separators=(',', ':'))
- 完成后,我们现在准备对请求主体进行标记。标记是一种方法,我们将大段的段落拆分为句子,并将句子拆分为单词。我们可以使用以下代码执行标记化:
tokenizer = Tokenizer(filters='\t\n', char_level=True)
tokenizer.fit_on_texts(X)
- 标记化完成后,我们将每个请求主体条目转换为向量。我们这样做是因为计算机需要对数据进行数值表示才能进行计算。然后,我们进一步将数据集分成两部分——75%的数据集用于训练,其余的用于测试。同样,使用以下代码将标签列拆分:
num_words = len(tokenizer.word_index)+1
X = tokenizer.texts_to_sequences(X)
max_log_length = 1024
split = int(len(df_values) * .75)
X_processed = sequence.pad_sequences(X, maxlen=max_log_length)
X_train, X_test = X_processed[0:split], X_processed[split:len(X_processed)]
Y_train, Y_test = Y[0:split], Y[split:len(Y)]
请记住,从第 2 步中可以看出,这些数据主要包含文本。在处理文本数据时,通常会有一个上下文和一个特定的顺序。
例如,考虑这句话中的单词——萨钦·坦度尔卡尔是一位伟大的板球运动员。为了传达预期的含义,单词的顺序不能改变。在处理机器学习中的文本数据时,维护顺序和上下文的重要性就体现在这里。
在我们的案例中,我们将使用一种特殊类型的递归神经网络——长短期记忆网络(LSTM),它将学习识别常规用户行为。
对 LSTM 的详细讨论超出了本书的范围,但如果您感兴趣,可以参考bit.ly/2m0RWnx
以获取详细信息。
- 现在,我们添加层和词嵌入,帮助保持数字编码文本与实际单词之间的关系,使用以下代码:
clf = Sequential()
clf.add(Embedding(num_words, 32, input_length=max_log_length))
clf.add(Dropout(0.5))
clf.add(LSTM(64, recurrent_dropout=0.5))
clf.add(Dropout(0.5))
clf.add(Dense(1, activation='sigmoid'))
我们的输出是一个单神经元,如果是非异常登录尝试则为 0
,如果是异常登录尝试则为 1
。
- 然后,我们使用以下代码编译模型并打印摘要:
clf.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
print(clf.summary())
模型的摘要已生成,如下所示:
现在,我们准备好继续训练模型:
- 我们使用模型的
fit()
方法,如下所示:
clf.fit(X_train, Y_train, validation_split=0.25, epochs=3, batch_size=128)
- 我们将快速检查模型的准确性。我们可以看到该模型在验证数据上的准确率超过了 96%。考虑到这是我们的第一个模型,这个分数相当令人印象深刻。我们可以使用以下代码检查模型的准确性:
score, acc = clf.evaluate(X_test, Y_test, verbose=1, batch_size=128)
print("Model Accuracy: {:0.2f}%".format(acc * 100))
你应该看到如下截图中的输出:
- 让我们保存这些权重。我们将使用它们创建一个用于认证用户的 API。我们可以使用以下代码保存模型:
clf.save_weights('weights.h5')
clf.save('model.h5')
模型准备好后,我们可以继续将其作为 Flask API 托管。
托管自定义认证验证模型
现在,让我们创建一个 API,该 API 将接受用户的登录尝试并返回对登录有效性的信心值:
- 我们首先导入创建 Flask 服务器所需的模块,如下所示:
from sklearn.externals import joblib
from flask import Flask, request, jsonify
from string import digits
import sys
import os
import json
import pandas
import numpy
import optparse
from keras.models import Sequential, load_model
from keras.preprocessing import sequence
from keras.preprocessing.text import Tokenizer
from collections import OrderedDict
- 现在,我们需要导入从
model
训练步骤中保存的模型和权重。一旦导入,我们需要重新编译模型并使用make_predict_function()
方法创建其predict
函数:
app = Flask(__name__)
model = load_model('lstm-model.h5')
model.load_weights('lstm-weights.h5')
model.compile(loss = 'binary_crossentropy', optimizer = 'adam', metrics = ['accuracy'])
model._make_predict_function()
- 我们将使用一个数据清理函数,从客户端应用程序中剥离数字和其他无用文本:
def remove_digits(s: str) -> str:
remove_digits = str.maketrans('', '', digits)
res = s.translate(remove_digits)
return res
-
接下来,我们在应用程序中创建
/login
路由,当用户尝试登录时,该路由将接受客户端应用程序的登录凭证和其他请求头信息。请注意,我们仍然像训练时那样去掉了一些额外的请求头。 -
清理完数据后,我们对其进行标记化和向量化。这些步骤与我们在训练期间所做的预处理相同。这样做是为了确保传入的请求与训练阶段处理方式一致:
@app.route('/login', methods=['GET, POST'])
def login():
req = dict(request.headers)
item = {}
item["method"] = str(request.method)
item["query"] = str(request.query_string)
item["path"] = str(request.path)
item["statusCode"] = 200
item["requestPayload"] = []
X = numpy.array([json.dumps(item)])
log_entry = "store"
tokenizer = Tokenizer(filters='\t\n', char_level=True)
tokenizer.fit_on_texts(X)
seq = tokenizer.texts_to_sequences([log_entry])
max_log_length = 1024
log_entry_processed = sequence.pad_sequences(seq, maxlen=max_log_length)
prediction = model.predict(log_entry_processed)
print(prediction)
response = {'result': float(prediction[0][0])}
return jsonify(response)
最后,应用程序以 JSON 格式返回其对用户认证的信心值。
- 要在所需端口上运行服务器,我们需要在脚本末尾添加以下几行:
if __name__ == '__main__':
app.run(port=9000, debug=True)
- 最后,我们将服务器脚本文件保存为
main.py
。我们将通过在系统上使用以下命令来启动服务器:
python main.py
这将启动 Flask 服务器,该服务器在回环 IP 127.0.0.1
和端口 9000
上监听。你可以轻松地将此脚本托管在云中的虚拟机上,并作为通用安全检查点 API,使其对所有应用程序和网站开放。
现在,我们可以继续创建在 Django 框架上运行的 Web 应用程序。
基于 Django 的应用程序,用于使用 API
我们创建的网站用于调用用户认证检查 API,将是一个简单的公告板演示。该网站将允许用户登录,然后将账单发布到公告板。虽然该应用程序很简单,但它包含基于深度学习的安全集成的两个主要功能——用户认证过程中的异常检测和发布账单时实现 reCAPTCHA——以避免垃圾邮件。
创建应用程序的步骤将在以下部分讨论。
Django 项目设置
在本节中,我们将使用 Django。请确保在继续本节之前,您的系统上已安装了 Django。您可以在第八章,《在 Microsoft Azure 上使用 Python 进行深度学习》一节中的 简要介绍 Django Web 开发 部分找到 Django 的安装说明。
现在,我们将创建一个 Django 项目。为此,我们使用以下命令:
django-admin startproject webapp
这将创建当前文件夹中的webapp
目录。我们将在此目录中添加所有未来的代码。当前的目录结构如下所示:
webapp/
manage.py
webapp/
__init__.py
settings.py
urls.py
wsgi.py
db.sqlite3
完成此步骤后,我们现在准备好在项目中创建应用程序,具体内容将在下一部分展示。
在项目中创建应用程序
如在第八章中讨论的,使用 Python 在 Microsoft Azure 上进行深度学习,我们现在必须将应用程序添加到网站项目中。为此,我们使用以下命令:
cd webapp
python manage.py startapp billboard
上述命令将创建一个名为billboard
的应用程序。然而,我们仍然需要将此应用程序与项目连接。
将应用程序链接到项目
要将应用程序添加到项目中,我们需要在项目设置文件settings.py
的应用程序列表中添加应用程序名称,如下所示。在settings.py
中,添加以下更改:
# Application definition
INSTALLED_APPS = [
'billboard', # <---- ADD THIS LINE
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
完成后,我们准备好在网站上创建路由。
向网站添加路由
要向项目添加路由,我们需要编辑webapp
的urls.py
文件:
from django.contrib import admin
from django.urls import path, include # <--- ADD 'include' module
urlpatterns = [
path('', include('billboard.urls')), # <--- ADD billboard.urls path
path('admin/', admin.site.urls),
]
然而,billboard.urls
路径并不存在。我们将创建该路径以继续。
在公告板应用中创建路由处理文件
在billboard
文件夹中创建一个名为urls.py
的新文件,如下所示:
from django.urls import path
from django.contrib.auth.decorators import login_required
from . import views
urlpatterns = [
path('', login_required(views.board), name='View Board'),
path('add', login_required(views.addbill), name='Add Bill'),
path('login', views.loginView, name='Login'),
path('logout', views.logoutView, name='Logout'),
]
将此保存为webapp/billboard/urls.py
。注意,我们已经将一些views
项导入到此路由处理文件中。同时,我们使用了login_required
方法。这表示我们可以开始进行网站的认证工作。
添加认证路由和配置
要添加认证的路由,在webapp/settings.py
文件的末尾添加以下内容:
LOGIN_URL = "/login"
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/logout'
这些行表示我们需要一个/login
和一个/logout
路由。
创建登录页面
要创建登录页面,我们需要将/login
路由添加到公告板应用中的urls.py
文件中。然而,我们已经完成了这一步。接下来,我们需要将loginView
视图添加到公告板应用的views.py
文件中:
def loginView(request):
if request.user.is_authenticated:
return redirect('/')
else:
if request.POST:
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
## MORE CODE BELOW THIS LINE
## MORE CODE ABOVE THIS LINE
else:
return redirect('/logout')
else:
template = loader.get_template('login.html')
context = {}
return HttpResponse(template.render(context, request))
上述函数首先检查传入的用户名和密码是否存在于用户数据库中。因此,我们将需要一个用户模型,将来将用户存储在db.sqlite3
数据库文件中,该文件是在项目创建步骤中创建的。
然后,函数将调用身份验证检查模型 API,以验证用户登录是否符合正常行为。验证过程如以下代码所示:
def loginView(request):
...
## MORE CODE BELOW THIS LINE
if user is not None:
url = 'http://127.0.0.1:9000/login'
values = { 'username': username, 'password': password }
data = urllib.parse.urlencode(values).encode()
req = urllib.request.Request(url, data=data)
response = urllib.request.urlopen(req)
result = json.loads(response.read().decode())
if result['result'] > 0.20:
login(request, user)
return redirect('/')
else:
return redirect('/logout')
## MORE CODE ABOVE THIS LINE
...
上述代码块验证用户登录,如果发现无效,则执行注销操作并将用户重定向回重新登录。
为此,我们需要在view.py
文件中添加一些必要的导入,如下所示:
from django.shortcuts import redirect
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse
from django.template import loader
from django.conf import settings
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.models import User
import urllib
import ssl
import json
注意,我们还从django.contrib.auth
导入了logout
方法。它将用于创建logout
视图。
创建注销视图
现在,让我们创建logout
视图。这非常简单,如下所示:
def logoutView(request):
logout(request)
return redirect('/')
现在,让我们创建登录页面的模板。
创建登录页面模板
要创建模板,我们首先需要创建所需的文件夹。
在billboard
目录下创建一个名为templates
的文件夹。目录结构将如下所示:
webapp/
manage.py
webapp/
__init__.py
settings.py
urls.py
wsgi.py
billboard/
templates/
___init_.py
admin.py
apps.py
models.py
tests.py
urls.py
views.py
在templates
文件夹中,我们将放置我们的模板文件。首先创建base.html
,我们将在所有其他模板中扩展它。它将包含CSS
和JS
引入,以及页面的通用块结构。
完成这些步骤后,我们就可以创建login.html
文件,它将执行将登录值发送到服务器的过程:
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="form_bg">
<form method="post">
{% csrf_token %}
<h2 class="text-center">Login Page</h2>
# WE'LL ADD MORE CODE BELOW THIS LINE
...
# WE'LL ADD MORE CODE ABOVE THIS LINE
</form>
</div>
</div>
</div>
{% endblock %}
注意,我们在前述视图模板中扩展了base.html
模板。
你可以在tutorial.djangogirls.org/en/template_extending/
了解更多关于扩展 Django 模板的信息。
登录页面中的表单发出POST
请求,因此需要传递 CSRF 令牌。现在我们可以创建在登录完成后渲染的页面。
公告板页面模板
由于我们已经设置了base.html
文件,因此可以简单地在board.html
模板文件中扩展它,以创建公告板展示页面:
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row">
{% for bill in bills %}
<div class="col-sm-4 py-2">
<div class="card card-body h-100">
<h2>{{ bill.billName }}</h2>
<hr>
<p>
{{ bill.billDesc }}
</p>
<a href="#" class="btn btn-outline-secondary">{{ bill.user.username }}</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
在之前的代码块中,我们遍历了公告板数据库中所有可用的bills
项,并通过for
循环在模板中显示它们。使用base.html
模板使我们能够减少视图模板中的重复代码量。
接下来,我们将创建一个页面,页面中将包含添加新账单到公告板的代码。
添加到公告板页面模板
要创建将账单添加到公告板的页面模板,我们使用以下代码创建add.html
模板文件:
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="form_bg">
<form method="post" id="form">
{% csrf_token %}
<h2 class="text-center">Add Bill</h2>
<br />
<div class="form-group">
<input type="text" class="form-control" id="billname" name="billname" placeholder="Bill Name">
</div>
<div class="form-group">
<input type="text" class="form-control" id="billdesc" name="billdesc" placeholder="Description">
</div>
<br />
<div class="align-center">
<button type="submit" class="btn btn-success" id="save">Submit</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
在前面的代码块中,我们扩展了 base.html
模板,添加了一个表单,允许我们添加账单。请注意在 form
元素中使用了 CSRF token。在 Django 中,我们在进行 POST 请求时,始终需要传递有效的 CSRF token。
您可以在 docs.djangoproject.com/en/3.0/ref/csrf/
阅读更多关于 CSRF token 的内容。
但是等等,我们还没有添加处理公告板页面和添加账单页面的视图。现在就来添加吧!
公告板模型
我们需要添加视图来查看公告板页面上的所有账单。不过,为此,我们首先需要创建一个模型来存储所有账单。
在 models.py
文件中,添加以下代码:
from django.utils.timezone import now
from django.contrib.auth.models import User
class Bills(models.Model):
billName = models.CharField("Bill Name", blank=False, max_length=100, default="New Bill")
user = models.ForeignKey(User, on_delete=models.CASCADE)
billDesc = models.TextField("Bill Description")
billTime = models.DateTimeField(default=now, editable=False)
class Meta:
db_table = "bills"
在前面的代码中,我们创建了一个名为 Bills
的新模型。这个模型将存储用户在公告板上添加的所有账单的详细信息。user
模型通过外键与这个模型关联。将此文件保存为 webapp/billboard/models.py
。
您可以在 www.sqlite.org/foreignkeys.html
阅读更多关于外键和其他键的信息。
完成这些步骤后,我们现在可以在视图中使用 Bills
模型了。
创建公告板视图
要在应用中开始使用 Bills
模型,首先需要将其导入到 views.py
文件中。
在 view.py
文件的顶部添加以下代码:
from .models import Bills
然后,我们可以添加公告板的视图,如下所示:
def board(request):
template = loader.get_template('board.html')
context = {}
context["isLogged"] = 1
Bill = Bills.objects.all()
context["bills"] = Bill
return HttpResponse(template.render(context, request))
接下来,我们需要创建一个用于添加账单的视图。
创建账单并添加视图
在这个视图中,我们将创建账单。如果收到有效的 POST
请求,我们会创建一个新的 Bill
对象并保存到数据库中。否则,我们会显示添加账单的表单给用户。我们来看一下如何在以下代码中实现:
def addbill(request):
if request.POST:
billName = request.POST['billname']
billDesc = request.POST['billdesc']
Bill = Bills.objects.create(billName=billName, user=request.user, billDesc=billDesc)
Bill.save()
return redirect('/')
else:
template = loader.get_template('add.html')
context = {}
context["isLogged"] = 1
return HttpResponse(template.render(context, request))
然而,在使用该应用之前,我们仍然需要创建管理员用户。
创建管理员用户并进行测试
要创建管理员用户,请使用以下命令:
python manage.py createsuperuser
现在,我们可以使用以下命令迁移数据库更改:
python manage.py makemigrations
python manage.py migrate
会生成如下所示的输出:
现在,让我们使用 reCAPTCHA 工具来保护公告板的发布内容。
在 Web 应用中使用 Python 和 reCAPTCHA
要将 reCAPTCHA 添加到网站中,首先需要从 Google reCAPTCHA 控制台获取 API 密钥:
-
首先,登录到您的 Google 账户,并访问
www.google.com/recaptcha
。 -
接下来,点击右上角的 Admin Console。
-
按照屏幕上的步骤将您的网站添加到控制台。如果您在本地系统上进行测试,您需要将
127.0.0.1
作为其中一个网址指定。 -
获取您的域名的 API 密钥。
您在获取域名 API 密钥的页面应该类似于以下截图:
- 现在,将密钥添加到 web 应用的
settings.py
文件中,如下所示:
GOOGLE_RECAPTCHA_SECRET_KEY = '6Lfi6ncUAAAAANJYkMC66skocDgA1REblmx0-3B2'
- 接下来,我们需要将要加载的脚本添加到
add.html
模板中。我们将其添加到广告牌应用页面模板中,如下所示:
<script src="img/api.js?render=6Lfi6ncUAAAAAIaJgQCDaR3s-FGGczzo7Mefp0TQ"></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('6Lfi6ncUAAAAAIaJgQCDaR3s-FGGczzo7Mefp0TQ')
.then(function(token) {
$("#form").append('<input type="hidden" name="g-recaptcha-response" value="'+token+'" >');
});
});
</script>
{% endblock %}
请注意,此步骤中使用的密钥是客户端/站点密钥。
- 最后,我们需要在添加广告牌视图中验证 reCAPTCHA,如下所示:
def addbill(request):
if request.POST:
recaptcha_response = request.POST.get('g-recaptcha-response')
url = 'https://www.google.com/recaptcha/api/siteverify'
values = { 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY,
'response': recaptcha_response}
context = ssl._create_unverified_context()
data = urllib.parse.urlencode(values).encode()
req = urllib.request.Request(url, data=data)
response = urllib.request.urlopen(req, context=context)
result = json.loads(response.read().decode())
if result['success']:
# Do stuff if valid
else:
# Do actions when no request is made
你可以通过以下链接获取前述代码块中的addbill
方法的完整工作版本:github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/blob/master/Chapter10/webapp/billboard/views.py
。
在完成前述更改后,我们终于准备好进行网站的测试,确保所有安全措施已到位。运行以下命令启动网站服务器:
python manage.py runserver
你应该能够看到网站的登录页面,如下所示:
请注意,此时,你需要同时运行 Flask 服务器来执行登录验证。
登录后,你将看到带有账单的广告牌页面。点击“添加账单”按钮以添加新账单,如下所示:
请注意屏幕右下角的 reCAPTCHA 标志。这表示该页面已通过使用 reCAPTCHA 防止垃圾信息。如果你能够成功提交,广告牌将显示已提交的账单。如果未成功提交,你将看到一个 reCAPTCHA 验证挑战。
使用 Cloudflare 提升网站安全性
Cloudflare 是业内领先的网络基础设施和网站安全提供商。它在网站与用户之间创建了一个安全层和快速内容传输层,从而将所有流量通过其服务器路由,这使得网站能够实现安全保护和其他功能。2017 年,Cloudflare 为超过 1200 万个网站提供了 DNS 服务。这些服务包括内容分发网络、分布式拒绝服务(DDoS)攻击保护、黑客攻击防护,以及其他互联网安全服务,如防止盗链。
2014 年,Cloudflare 报告成功缓解了一次 400 Gib/s 的 DDoS 攻击,目标是某客户,随后在第二年发生了 500 Gib/s 的攻击。迄今为止,最大规模的 DDoS 攻击发生在 GitHub,GitHub 遭遇了 1.4Tb/s 的洪水式攻击。GitHub 当时使用的是 Akamai Prolexic(Cloudflare 的替代品),成功抵御了这次攻击,网站仅停机了 10 分钟后就完全恢复了。Cloudflare 为所有用户免费提供 DDoS 防护。
要开始在你的网站上部署 Cloudflare 服务,你需要将 Cloudflare 设置为用户与托管服务器之间的中介层。以下图示展示了 Cloudflare 如何在网络中部署:
因此,通过 Google 的 reCAPTCHA 的帮助,我们之前为垃圾邮件和恶意用户创建的定制解决方案,Cloudflare 已在基础层面(免费层次中,升级后更强大的解决方案会出现在更高的层级)自动处理了。这使得小型开发团队能够轻松地将安全需求交给 Cloudflare 的系统,并放心他们会受到保护,免受一系列安全漏洞的影响。
总结
在本章中,我们学习了如何利用 Cloudflare 的服务为网站提供安全保护。我们还学习了如何创建可以与 Web 应用程序以及其他安全服务(如 reCAPTCHA)集成使用的安全 API。对于任何网站——无论大小——都至关重要,必须实施这些安全措施,以确保其网站服务正常运行。近期发生了重大数据泄露事件,还有许多尝试被 AI 驱动的系统进行的攻击,尽管它们没有成为新闻报道,因为它们并未引起问题。利用深度学习进行安全防护是一个热门的研究课题,预计在不久的将来,所有的安全系统将会高度依赖深度学习来识别和消除威胁。
在下一章中,我们将讨论如何设置一个生产级别的深度学习环境。我们将讨论根据需求规模,可以遵循的架构设计,以及最先进的服务提供商和工具。
第十五章:DIY - 一个 Web DL 生产环境
在之前的章节中,我们看到如何使用一些著名的深度学习(DL)平台,如亚马逊网络服务(AWS)、谷歌云平台(GCP)和微软 Azure,在我们的 Web 应用中实现 DL。接着,我们了解了如何使用 DL 使网站变得更加安全。然而,在生产环境中,挑战通常不仅仅是构建预测模型——真正的问题出现在你想要更新一个已经向用户发送响应的模型时。替换模型文件可能需要 30 秒或 1 分钟,那么你可能会损失多少时间和业务?如果每个用户都有定制的模型呢?这甚至可能意味着像 Facebook 这样的平台需要数十亿个模型。
你需要为生产环境中的模型更新制定明确的解决方案。此外,由于输入的数据可能不是训练时所用格式,你需要定义数据流,使其无缝地转换为可用格式。
在本章节中,我们将讨论在生产环境中更新模型的方法,以及选择每种方法时的思考过程。我们将从简要概述开始,然后展示一些著名的工具,用于创建 DL 数据流。最后,我们将实现自己的在线学习或增量学习演示,以建立一种在生产环境中更新模型的方法。
本章节将涵盖以下主题:
-
生产中深度学习方法概览
-
在生产中部署 ML 的流行工具
-
实现一个演示型 DL Web 生产环境
-
将项目部署到 Heroku
-
安全性、监控和性能优化
技术要求
你可以在github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter11
访问本章节的代码。
你需要以下软件来运行本章节中的代码:
-
Python 3.6+
-
Flask 1.1.12+
所有其他安装将在本章节中完成。
生产中深度学习方法概览
无论是深度学习(DL)还是经典的机器学习(ML),在生产中使用模型时都会面临挑战。主要原因是数据驱动着 ML,而数据随着时间的推移可能会发生变化。当 ML 模型部署到生产环境中时,随着数据不断变化,模型需要在特定的时间间隔内重新训练。因此,在考虑生产应用时,重新训练 ML 不仅仅是一种奢侈,而是一种必要性。DL 只是 ML 的一个子领域,因此它也不例外。ML 模型的训练有两种常见方法——批量学习和在线学习,尤其是在生产环境中。
我们将在下一节中讨论在线学习。在本节中,让我们先介绍一下批量学习的概念。在批量学习中,我们首先在一块特定的数据上训练机器学习模型,当模型完成该块数据的训练后,便会输入下一块数据,并继续这个过程,直到所有数据块都被用完。这些数据块被称为批次。
在实际项目中,你会一直处理大量数据。将这些数据集一次性加载到内存中并不是理想的做法。批量学习在这种情况下就能派上用场。使用批量学习有其缺点,我们将在下一节讨论这些缺点。你可能会好奇(或者可能不会),但的确,在本书中,我们每次训练神经网络时都会进行批量学习。
和训练一样,批次的概念也可以应用于服务机器学习(ML)模型。这里所说的“服务”机器学习模型,指的是使用机器模型对未见过的数据点进行预测。这也被称为推理。现在,模型服务可以分为两种类型——在线服务,即模型一旦接触到数据点就需要立即做出预测(在这里我们无法容忍延迟),以及离线服务,即先收集一批数据点,再通过模型对这批数据点进行预测。在第二种情况下,我们可以容忍一些延迟。
请注意,还有一些与生产机器学习系统直接相关的工程方面。讨论这些内容超出了本书的范围,但我们鼓励你查阅 GCP 团队的在线课程。
让我们尝试通过以下图表总结并进一步理解之前的讨论:
该图表描述了你的 AI 后台需求及可能影响解决方案选择的各种参数。我们将在下一节中讨论与此图表相关的所有方面和可用的选择。
所以,我们有四种主要的解决方案类型,通常可以在深度学习的生产环境中看到它们:
-
一个 Web API 服务
-
在线学习
-
批量预测
-
自动化机器学习(Auto ML)
让我们详细地看一下每一种方法。
一个 Web API 服务
我们有一个由后台独立脚本训练的模型,它被存储为一个模型并作为基于 API 的服务进行部署。在这里,我们看到的是一种按需生成结果的解决方案,但训练发生在离线(即不在负责响应客户端查询的代码部分的执行期间)。Web API 一次响应一个查询,并产生单一的结果。
迄今为止,这是在生产环境中部署深度学习(DL)最常用的方法,因为它允许数据科学家离线进行准确的训练,并且通过一个简短的部署脚本来创建 API。在本书中,我们大多数情况下进行了这种类型的部署。
在线学习
通过后端实现按需预测的另一种形式是在线学习。然而,在这种方法中,学习发生在服务器脚本的执行过程中,因此模型随着每个相关查询而不断变化。虽然这种方法是动态的,不容易过时,但它通常比其静态对手——Web API 更不准确。在线学习同样每次产生一个结果。
在本章中,我们展示了在线学习的一个示例。我们将在接下来的章节中讨论对在线学习有帮助的工具。
批量预测
在这种方法中,会一次性做出多个预测,并将其存储在服务器上,随时准备在用户需要时提取和使用。然而,作为一种静态训练方法,这种方法允许离线训练模型,因此提供更高的训练准确性,类似于 Web API。
换句话说,批量预测可以理解为 Web API 的批量版本;然而,预测并不是通过 API 提供的,而是存储在数据库中并从中提取。
自动化机器学习(Auto ML)
做出预测只是将深度学习(DL)应用于生产的整个过程中的一部分。数据科学家还负责清洗和整理数据,创建管道以及优化。自动化机器学习(Auto ML)是一种消除这些重复任务需求的方法。
自动化机器学习(Auto ML)是一种批量预测方法,消除了对人工干预的需求。因此,数据在到达时会经过一个管道,预测会定期更新。因此,这种方法比批量预测方法提供了更为及时的预测。
现在让我们讨论一些快速实现我们所展示的某些方法的工具。
部署 ML 到生产环境中的流行工具
在本节中,我们将讨论一些用于将机器学习(ML)应用于生产系统的流行工具。这些工具提供的核心功能是自动化学习-预测-反馈流程,并促进模型质量和性能的监控。虽然完全可以创建自己的工具来实现这些功能,但强烈推荐根据软件的需求使用以下任何一种工具。
让我们首先讨论一下 creme
。
creme
creme
是一个 Python 库,它使我们能够高效地进行在线学习。在我们实际操作 creme
之前,让我们简要讨论一下在线学习本身:
在在线学习中,ML 模型是逐个实例进行训练,而不是批量数据训练(这也被称为批量学习)。要理解在线学习的使用,首先要了解批量学习的缺点:
-
在生产环境中,我们需要随着时间的推移在新数据上重新训练机器学习模型。批量学习迫使我们这样做,但这会带来成本。成本不仅体现在计算资源上,还体现在模型需要从头开始重新训练。重新从头训练模型在生产环境中并不总是有用的。
-
数据的特征和标签可能会随时间变化。批量学习不允许我们训练可以支持动态特征和标签的机器学习模型。
这正是我们需要使用在线学习的地方,它使我们能够做到以下几点:
-
使用单个实例逐步训练机器学习模型。因此,我们无需一批数据来训练机器学习模型;可以在数据到达时即时训练模型。
-
使用动态特征和标签训练机器学习模型。
在线学习有几个其他名称,但它们做的都是相同的事情:
-
增量学习
-
顺序学习
-
迭代学习
-
外部学习
如前所述,creme
是一个用于进行在线学习的 Python 库。在处理生产环境时,它是你机器学习工具箱中一个非常有用的工具。creme
深受 scikit-learn(一个非常流行的 Python 机器学习库)的启发,使得它非常易于使用。为了全面了解creme
,建议你查看creme
的官方 GitHub 仓库:github.com/creme-ml/creme
。
够多的理论了!让我们先安装creme
。可以使用以下命令完成:
pip install creme
要获取最新版本的creme
,可以使用以下命令:
pip install git+https://github.com/creme-ml/creme
# Or through SSH:
pip install git+ssh://git@github.com/creme-ml/creme.git
让我们通过以下步骤来快速查看一个例子:
- 我们首先从
creme
模块进行一些必要的导入:
from creme import compose
from creme import datasets
from creme import feature_extraction
from creme import metrics
from creme import model_selection
from creme import preprocessing
from creme import stats
from creme import neighbors
import datetime as dt
注意,creme
的命名规范类似于sklearn
库,便于迁移体验。
- 然后,我们从
creme
模块本身获取一个数据集,并将其赋值给变量 data:
data = datasets.Bikes()
我们将处理一个包含共享骑行信息的数据集。
虽然数据集包含在creme
库中,但你可以在archive.ics.uci.edu/ml/datasets/bike+sharing+dataset
上阅读更多相关信息。
- 接下来,我们使用
creme
构建一个管道,如下所示:
model = compose.Select("humidity", "pressure", "temperature")
model += feature_extraction.TargetAgg(by="station", how=stats.Mean())
model |= preprocessing.StandardScaler()
model |= neighbors.KNeighborsRegressor()
请注意|=
和+=
运算符的使用。creme
使得这些运算符得以使用,这使得理解数据管道变得非常直观。我们可以通过使用以下命令,获取前一个代码块中构建的管道的详细表示:
model
上一个命令的输出如下所示:
Pipeline([('TransformerUnion', TransformerUnion (
Select (
humidity
pressure
temperature
),
TargetAgg (
by=['station']
how=Mean ()
target_name="target"
)
)), ('StandardScaler', StandardScaler (
with_mean=True
with_std=True
)), ('KNeighborsRegressor', KNeighborsRegressor([]))])
我们还可以通过使用以下命令,获取该管道的可视化表示:
model.draw()
这将生成以下图表:
- 最后,我们运行训练并每隔 30,000 行数据集计算一次评分指标。在生产服务器上,这段代码将在每分钟进行一次批量预测:
model_selection.progressive_val_score(
X_y=data,
model=model,
metric=metrics.RMSE(),
moment='moment',
delay=dt.timedelta(minutes=1),
print_every=30_000
)
所以,creme
通过其简洁的语法和调试功能,使得在生产环境中创建批量预测和在线学习部署变得非常简单。
现在我们来讨论另一个流行的工具——Airflow。
Airflow
作为一个有效的机器学习从业者,你需要通过编程的方式处理像上面那样的工作流,并且能够自动化它们。Airflow 为你提供了一个平台,使得这一切变得高效。这个链接——airflow.apache.org
——摘自 Airflow 的官方网站。Airflow 是一个用于以编程方式创建、调度和监控工作流的平台。
这样做的主要优势是,有向无环图(DAGs)中表示的任务可以轻松地分配到可用资源(通常称为工作节点)上。它还使得可视化整个工作流变得更加容易,这对于工作流非常复杂时尤为有用。如果你需要复习 DAGs 的相关内容,可以参考这篇文章:cran.r-project.org/web/packages/ggdag/vignettes/intro-to-dags.html
。当你看到实际的实现时,这一概念会变得更加清晰。
当你设计一个机器学习工作流时,需要考虑许多不同的因素,例如以下内容:
-
数据收集管道
-
数据预处理管道
-
使数据可供机器学习模型使用
-
机器学习模型的训练和评估管道
-
模型的部署
-
监控模型,以及其他事项
现在,我们通过执行以下命令来安装 Airflow:
pip install apache-airflow
虽然 Airflow 是基于 Python 的,但完全可以使用 Airflow 来定义包含不同任务语言的工作流。
安装完成后,你可以调用 Airflow 的管理面板,查看其中 DAG 的列表,管理它们并触发其他许多有用的功能,如下所示:
- 为此,你必须首先初始化数据库:
airflow initdb
- 你应该能看到在
SQLite3
数据库上创建了多个表。如果成功,你将能够通过以下命令启动 Web 服务器:
airflow webserver
在浏览器中打开http://localhost:8080
,你将看到如下截图所示的页面:
提供了多个示例 DAG(有向无环图)。你可以尝试运行它们,快速体验一番!
现在我们来讨论一个非常流行的工具——AutoML。
AutoML
在工业使用中,深度学习或人工智能解决方案不仅仅局限于在 Jupyter Notebook 中构建最先进的精确模型。形成 AI 解决方案有几个步骤,从收集原始数据、将数据转换为可用于预测模型的格式、创建预测、围绕模型构建应用程序,到在生产中监控和更新模型。AutoML 旨在通过自动化部署前任务来自动化这一过程。通常,AutoML 主要涉及数据编排和贝叶斯超参数优化。AutoML 并不总是指完全自动化的学习管道。
一个著名的 AutoML 库由H2O.ai
提供,名为H2O.AutoML
。要使用它,我们可以通过以下命令安装:
# Using Conda installer
conda install -c h2oai h2o
# Using PIP installer
pip install -f http://h2o-release.s3.amazonaws.com/h2o/latest_stable_Py.html h2o
H2O.AutoML
由于其语法与其他流行的机器学习库相似,因此非常容易理解。
实现一个演示的深度学习 Web 环境
我们现在将深入研究如何构建一个使用在线学习作为后端的示例生产应用程序。我们将创建一个可以根据 Cleveland 数据集预测心脏病的应用程序。然后,我们将把这个模型部署到 Heroku,它是一个基于云容器的服务。最后,我们将演示应用程序的在线学习功能。
你可以通过访问heroku.com
了解更多关于 Heroku 的信息。
让我们列出我们将要覆盖的步骤:
-
在 Jupyter Notebook 上构建一个预测模型。
-
为 Web 应用程序构建一个后端,用于对保存的模型进行预测。
-
为 Web 应用程序构建一个前端,用于在模型上调用增量学习。
-
在服务器端逐步更新模型。
-
将应用程序部署到 Heroku。
我们将从零步骤开始;也就是观察数据集。
UCI 心脏病数据集包含 303 个样本,每个样本有 76 个属性。然而,大多数关于该数据集的研究工作集中在 Cleveland 数据集的简化版本上,该版本有 13 个属性,如下所示:
-
年龄
-
性别
-
胸痛类型:
-
典型心绞痛
-
非典型心绞痛
-
非心绞痛性疼痛
-
无症状
-
-
静息血压
-
血清胆固醇(mg/dl)
-
空腹血糖 > 120 mg/dl
-
静息心电图结果:
-
正常
-
有 ST-T 波异常(T 波倒置和/或 ST 段抬高或压低超过 0.05 mV)
-
根据 Estes 标准,显示可能或确诊的左心室肥大
-
-
达到的最大心率
-
运动诱发的心绞痛
-
Oldpeak = 运动引起的 ST 压低与静息时相比
-
峰值运动 ST 段的坡度
-
主要血管数(0-3),通过荧光透视着色
-
Thal: 3 = 正常;6 = 固定缺陷;7 = 可逆缺陷
将会有一个最终列,它是我们要预测的目标。这将使得当前问题成为对正常患者和受影响患者之间的分类。
你可以在archive.ics.uci.edu/ml/datasets/Heart+Disease
阅读更多关于 Cleveland 数据集的信息。
现在我们开始构建心脏病检测模型。
构建预测模型
在这一小节中,我们将首先使用 Keras 构建一个简单的神经网络,该网络将从给定的输入中分类预测患者是否患有心脏病的概率。
第 1 步 – 导入必要的模块
我们首先导入所需的库:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
np.random.seed(5)
我们已经导入了pandas
和numpy
模块,并且还从 scikit-learn 库中导入了train_test_split
方法,帮助我们快速将数据集拆分为训练集和测试集。
第 2 步 – 加载数据集并观察
假设数据集存储在一个名为data
的文件夹中,并且该文件夹与包含我们的 Jupyter notebook 的文件夹位于同一目录级别,我们将加载数据集:
df = pd.read_csv("data/heart.csv")
我们将快速观察 DataFrame,查看所有列是否已正确导入:
df.head(5)
这将在 Jupyter notebook 中产生如下输出:
我们可以观察到 14 个列,并确认它们已经正确导入。一个基本的探索性数据分析(EDA)将揭示该数据集没有缺失值。然而,原始的 UCI Cleveland 数据集包含缺失值,这与我们使用的版本不同,后者已经过预处理并以这种形式在互联网上可以轻松获取。你可以在 GitHub 上本章的仓库中找到它,地址是tiny.cc/HoPforDL-Ch-11
。
第 3 步 – 分离目标变量
我们现在将从数据集中剔除目标变量,如下所示:
X = df.drop("target",axis=1)
y = df["target"]
接下来,我们将对特征进行缩放。
第 4 步 – 对特征进行缩放
如你在前一步中数据集的示例中所观察到的,训练列中的数值并不在统一的或可比较的范围内。我们将对这些列进行缩放,使它们达到一个统一的范围分布,如下所示:
from sklearn.preprocessing import StandardScaler
X = StandardScaler().fit_transform(X)
目标变量的范围是0
到1
,因此不需要进行缩放。
第 5 步 – 将数据集拆分为测试集和训练集
然后,我们将数据集拆分为训练部分和测试部分,使用以下代码行:
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.20,random_state=0)
我们已经将 20%的数据集分配用于测试。
第 6 步 – 在 sklearn 中创建神经网络对象
接下来,我们通过实例化一个新的MLPClassifier
对象来创建分类器模型的实例:
from sklearn.neural_network import MLPClassifier
clf = MLPClassifier(max_iter=200)
我们已将最大迭代次数随意设置为200
。如果收敛发生得更早,可能不会达到这个次数。
第 7 步 – 进行训练
最后,我们进行训练,并记录方法的观察准确率:
for i in range(len(X_train)):
xt = X_train[i].reshape(1, -1)
yt = y_train.values[[i]]
clf = clf.partial_fit(xt, yt, classes=[0,1])
if i > 0 and i % 25 == 0 or i == len(X_train) - 1:
score = clf.score(X_test, y_test)
print("Iters ", i, ": ", score)
在 Jupyter Notebook 中,前一块代码的输出如下:
我们可以看到,在对所有 241 个处理过的样本进行训练后,模型的准确度预计将达到 83.60%。注意前面代码块中的partial_fit
方法。这是模型的一个方法,用于将一个简单的样本拟合到模型中。更常用的fit
方法实际上是partial_fit
方法的一个包装器,它遍历整个数据集并在每次迭代中训练一个样本。它是我们演示使用 scikit-learn 库进行增量学习的最重要部分之一。
为了快速查看模型输出的格式,我们运行以下代码块:
# Positive Sample
clf.predict(X_test[30].reshape(-1, 1).T)
#Negative Sample
clf.predict(X_test[0].reshape(-1, 1).T)
以下是获得的输出:
注意,预测输出为0
的样本表示此人没有心脏病,而输出为1
的样本则表示此人患有心脏病。
我们现在将开始将这个 Jupyter 笔记本转换成一个可以按需增量学习的脚本。不过,我们首先将构建该项目的前端,以便我们能从后端理解需求。
实现前端
我们将采取自下而上的方法,首先设计我们示例应用程序的前端。这仅仅是为了理解为何我们在后端脚本中编写某些方法时与之前章节的做法不同。显然,在开发实际应用时你会首先创建后端脚本。
我们将有一个非常简化的前端,仅包含一个按钮,触发应用程序的增量训练,以及一个占位符,显示已训练至一定样本数量时的模型准确度。
让我们快速看一下我们将要构建的内容:
如你从我们将要构建的应用程序的前面截图中所见,我们将有两个按钮——一个按钮会将训练数据集中的 25 个样本添加到部分训练的模型中,另一个按钮会将训练重置为 0 个样本(实际上,在实现中这是 1 个样本,为了避免由 0 引起的常见错误;但这对演示的影响很小)。
我们将创建一个名为app
的 Flask 项目文件夹。然后在其中创建templates
文件夹,并在其中创建index.html
。在app
文件夹中创建另一个名为app.py
的文件。我们将在这个文件夹中创建更多文件,以便部署到 Heroku。
我们不会编写完整的index.html
文件代码,但我们会看一下这两个通过 Ajax 触发调用后端 API 的函数。
你可以在tiny.cc/HoPforDL-Ch-11-index
找到完整代码。
查看index.html
中的第109
行到第116
行:
....
$("#train-btn").click(function() {
$.ajax({
type: "POST",
url: "/train_batch",
dataType: "json",
success: function(data) {
console.log(data);
....
前面的这段 JavaScript(jQuery)代码为一个 ID 为train-btn
的按钮创建了一个click
事件处理程序。它调用了后端的/train_batch
API。我们将在开发后端时创建这个 API。
这个文件中另一个有趣的代码块是第138
行到第145
行:
....
$("#reset-btn").click(function() {
$.ajax({
type: "POST",
url: "/reset",
dataType: "json",
success: function(data) {
console.log(data);
....
在这里,我们为按钮设置了一个click
事件处理程序,按钮的 ID 为reset-btn
,该处理程序会触发对/reset
API 的请求。这是增量学习中一个容易被忽视的部分,它要求减少训练量;也就是说,它将训练过的模型重置为未训练的状态。
我们现在知道了需要在后端构建的 API。让我们在下一节中进行构建!
实现后端
在本节中,我们将着手创建所需的 API 以及演示的服务器脚本。编辑项目根目录中的app.py
文件:
- 首先,我们将对脚本进行一些必要的导入:
from flask import Flask, request, jsonify, render_template
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPClassifier
np.random.seed(5)
请注意,这里的导入与我们在 Jupyter Notebook 中创建模型时的导入非常相似。这是因为我们只是将 Jupyter Notebook 中的代码转换为用于后端演示的服务器脚本。
- 然后,我们将数据集加载到一个
pandas
DataFrame 中:
df = pd.read_csv("data/heart.csv")
- 我们将快速浏览剩余的代码,其中我们将拆分数据集、缩放列,并在一定数量的样本上训练模型:
X = df.drop("target",axis=1)
y = df["target"]
X = StandardScaler().fit_transform(X)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.20,random_state=0)
clf = MLPClassifier(max_iter=200)
for i in range(100):
xt = X_train[i].reshape(1, -1)
yt = y_train.values[[i]]
clf = clf.partial_fit(xt, yt, classes=[0,1])
if i > 0 and i % 25 == 0 or i == len(X_train) - 1:
score = clf.score(X_test, y_test)
print("Iters ", i, ": ", score)
请注意,在前面的代码中,我们在数据集的100
个样本上训练模型。这将使模型相当准确,但显然仍有改进的空间,我们将在使用/train_batch
API 时触发该改进,它会将 25 个样本添加到模型的训练中。
- 让我们设置一些变量以供脚本使用,并实例化
Flask
服务器对象:
score = clf.score(X_test, y_test)
app = Flask(__name__)
start_at = 100
- 我们现在将创建
/train_batch
API,如下所示:
@app.route('/train_batch', methods=['GET', 'POST'])
def train_batch():
global start_at, clf, X_train, y_train, X_test, y_test, score
for i in range(start_at, min(start_at+25, len(X_train))):
xt = X_train[i].reshape(1, -1)
yt = y_train.values[[i]]
clf = clf.partial_fit(xt, yt, classes=[0,1])
score = clf.score(X_test, y_test)
start_at += 25
response = {'result': float(round(score, 5)), 'remaining': len(X_train) - start_at}
return jsonify(response)
train_batch()
函数通过25
个样本或数据集中剩余的样本增加模型的学习量。它返回模型在数据集 20%测试集上的当前得分。再次注意,partial_fit
方法被用于 25 次迭代。
- 接下来,我们将创建
/reset
API,它将重置模型为未训练状态:
@app.route('/reset', methods=['GET', 'POST'])
def reset():
global start_at, clf, X_train, y_train, X_test, y_test, score
start_at = 0
del clf
clf = MLPClassifier(max_iter=200)
for i in range(start_at, start_at+1):
xt = X_train[i].reshape(1, -1)
yt = y_train.values[[i]]
clf = clf.partial_fit(xt, yt, classes=[0,1])
score = clf.score(X_test, y_test)
start_at += 1
response = {'result': float(round(score, 5)), 'remaining': len(X_train) - start_at}
return jsonify(response)
这个 API 再次返回重置后的模型得分。它应该如预期般——非常差——假设数据集在其类别中是平衡的。
- 现在,让我们编写代码以启动这个应用程序的 Flask 服务器:
@app.route('/')
def index():
global score, X_train
rem = (len(X_train) - start_at) > 0
return render_template("index.html", score=round(score, 5), remain = rem)
if __name__ == '__main__':
app.run()
- 完成此操作后,我们准备通过从控制台运行应用程序来测试它是否正常工作。为此,请打开一个新的终端窗口,并在
app
目录中输入以下命令:
python app.py
一旦服务器运行,您可以通过http://localhost:5000
查看应用程序。
最后,我们将把项目部署到 Heroku。
将项目部署到 Heroku
在本节中,我们将了解如何将我们的演示应用程序部署到 Heroku。在接下来的步骤中,我们将创建 Heroku 帐户并添加所需的代码修改,使其可以在该平台上托管:
- 首先,访问
id.heroku.com/login
获取 Heroku 的登录界面。如果你还没有用户帐户,可以通过注册过程免费创建一个帐户:
- 我们现在将创建一个
Procfile
文件。在此步骤中,我们在app
目录中创建一个名为Procfile
的空文件。创建后,我们在其中添加以下一行:
web: gunicorn app:app
该文件在将项目部署到 Heroku 时使用。前面的命令指示 Heroku 系统使用 gunicorn
服务器并运行名为 app.py
的文件。
- 然后,我们将冻结项目的依赖关系。Heroku 会查找
requirements.txt
文件,以自动下载并安装项目所需的包。要创建依赖列表,请在终端中使用以下命令:
pip freeze > requirements.txt
这将在项目的根文件夹中创建一个名为 requirements.txt
的文件,列出所有包。
你可能希望将某些包排除在 requirements.txt
文件之外。处理此类项目的好方法是使用虚拟环境,这样只有所需的包在环境中可用,requirements.txt
中只包含它们。然而,这种解决方案并不总是可行。在这种情况下,可以手动编辑 requirements.txt
并删除不相关的包。
项目的目录结构当前应如下所示:
app/
---- templates/
-------- index.html
---- Procfile
---- requirements.txt
---- app.py
-
现在,我们需要在本地系统上安装 Heroku CLI。请按照
devcenter.heroku.com/articles/heroku-cli
中提供的说明在系统上安装 Heroku。 -
接下来,我们将在目录上初始化
git
。为此,请在项目根目录中使用以下命令:
git init
- 然后,我们初始化项目的 Heroku 版本管理。打开终端窗口并导航到项目目录。使用以下命令初始化 Heroku 提供的版本管理器,并将其与当前登录的用户注册:
heroku create
该命令将以显示项目将托管的网址结束。同时,还会显示一个 .git
URL,用于跟踪项目的版本。你可以从这个 .git
URL 推送/拉取以更改项目并触发重新部署。输出将类似于以下内容:
https://yyyyyy-xxxxxx-ddddd.herokuapp.com/ | https://git.heroku.com/yyyyyy-xxxxxx-ddddd.git
- 接下来,我们将文件添加到
git
并推送到 Heroku。现在,你可以将文件推送到 Heroku 的git
项目进行部署。我们使用以下命令:
git add .
git commit -m "some commit message"
git push heroku master
这将创建部署并显示一个长时间的输出流。该流是部署过程中发生事件的日志——安装包、确定运行时、启动监听脚本。一旦你看到成功部署的消息,你就可以通过 Heroku 在上一阶段提供的 URL 查看你的应用。如果你记不住该 URL,你可以使用以下命令从终端触发它在浏览器中打开:
heroku open
现在,你应该在默认浏览器中看到一个新窗口或标签页打开,显示已部署的代码。如果出现问题,你可以在 Heroku 仪表盘中查看部署日志,如下所示:
这是一个实际的截图,展示了在部署本章所示代码时构建失败的情况。你应该能够看到日志末尾的错误信息。
如果构建部署成功,你将在日志末尾看到成功部署的消息。
安全措施、监控技术和性能优化
本节我们将讨论可以集成到生产中的深度学习解决方案的安全措施、监控技术和性能优化。这些功能对维护依赖于 AI 后端的解决方案至关重要。虽然我们在前几章中讨论了深度学习所促进的安全方法,但我们将讨论可能对 AI 后端构成的安全威胁。
对 AI 后端的最大安全威胁之一来自噪声数据。在大多数用于将 AI 投入生产的方法中,定期检查数据集中的新噪声类型非常重要,尤其是它所训练的数据集。
这是给所有喜爱 Python pickle
库的开发者们的重要提示:
上述截图来自官方 Python 文档,网址为 docs.python.org/3/library/pickle.html
。
为了展示生产中使用 pickle 可能带来的危险,请考虑以下 Python 代码:
data = """cos
system
(S'rm -ri ~'
tR.
"""
pickle.loads(data)
上述代码所做的事情很简单——它试图清除你的主目录。
警告:任何运行上述代码的人,须对其行为的结果承担全部责任。
上述示例及相关警告揭示了 AI 后端及几乎所有自动化系统中的一般安全威胁——不受信任输入的危害。因此,确保任何可能输入模型的数据,无论是在训练还是测试过程中,都经过适当验证,以确保它不会对系统造成任何严重问题,是非常重要的。
同样,对于生产环境中的模型,进行持续监控也非常重要。模型常常会变得过时和陈旧,随着时间的推移,它们可能会做出过时的预测。重要的是要检查 AI 模型所做的预测是否仍然相关。想象一下,一个只了解 CD-ROM 和软盘的人。随着时间的推移,我们出现了 USB 驱动器和固态硬盘。这个人将无法就现代设备做出任何智能决策。类似地,一个自然语言处理(NLP)模型,如果是在 2000 年代初期的文本资料上训练的,就无法理解像“你能不能通过 WhatsApp 给我发 Avengers: Endgame 的维基链接?”这样的问题。
最后,如何为人工智能后端的性能优化提供解决方案?
Web 开发人员通常最关心这个问题。一切在生产环境中都需要是闪电般的快速。加速生产环境中 AI 模型的一些技巧如下:
-
将数据集拆分为最低数量的特征,以便通过这些特征可以做出相对准确的预测。这是多个算法执行特征选择的核心思想,如主成分分析和其他启发式方法。通常,输入系统的所有数据并非都与预测相关,或者仅与预测的相关性很小。
-
考虑将你的模型托管在一个单独的、强大的云服务器上,并启用自动扩展功能。这将确保你的模型不会浪费资源来为网站服务页面,而只处理基于人工智能的查询。自动扩展将处理后端突增或剧烈减少的工作负载。
-
在线学习和自动化机器学习方法会受到数据集大小引起的速度问题的影响。确保你已经设置了约束条件,以防止动态学习系统处理的数据量膨胀。
摘要
在本章中,我们讨论了可以用来将深度学习模型部署到生产环境中的方法。我们详细查看了不同的方法和一些有助于简化生产环境部署和模型管理的著名工具。我们展示了使用 Flask 和sklearn
库进行在线学习的演示。我们还讨论了部署后的要求以及一些最常见任务的示例。
在下一章中,我们将展示一个端到端的示例应用——一个客户支持聊天机器人——它通过集成到网站中的 Dialogflow 来实现。
第十六章:使用 DL API 和客户支持聊天机器人创建 E2E Web 应用程序
在本章中,我们将整合之前章节中学习过的多个工具和方法,并引入一些全新的工具和技术。本章涵盖了企业非常重要的一个方面——客户支持。对于一个新兴的企业来说,客户支持可能既耗时又让人沮丧。客户提出的问题,通常通过查阅公司网站上提供的文档或常见问题解答(FAQ)就能轻松解答,但客户往往不愿意仔细阅读这些内容。因此,能够引入一层自动化,利用聊天机器人来回答最常见的查询,并确保该聊天机器人全天候响应,将会是一个极好的解决方案。
本章讨论如何使用 Dialogflow 创建聊天机器人以解决一般的客户支持问题,并将其集成到基于 Django 的网站中。此外,聊天机器人将从 Django API 中获取答案,该 API 将被单独托管。我们将探索如何实现聊天机器人个性,并介绍如何通过 Web Speech API 实现基于语音合成(TTS)和语音转文本(STT)的用户界面,该 API 将神经网络直接部署到用户的浏览器中。
本章将涵盖以下主题:
-
自然语言处理(NLP)简介
-
聊天机器人简介
-
创建一个拥有客户支持代表个性的 Dialogflow 聊天机器人
-
使用 ngrok 来便利本地 HTTPS API
-
使用 Django 创建一个管理公司内部订单的测试用户界面
-
使用 Web Speech API 在网页上实现语音识别和语音合成
我们将基于之前章节所学的内容,进一步深入并加以扩展,同时修正一些概念,并在过程中引入新的概念。让我们从了解自然语言处理(NLP)开始。
技术要求
你可以在github.com/PacktPublishing/Hands-On-Python-Deep-Learning-for-Web/tree/master/Chapter12
访问本章的代码。
运行本章代码所需的软件:
-
Python 3.6+
-
Django 2.x
其他所有安装内容将在本章中进行讲解。
自然语言处理(NLP)简介
自然语言处理(NLP)是机器学习和深度学习应用中一个受欢迎且令人兴奋的领域,它指的是一系列旨在理解和生成自然语言的技术和方法。NLP 的目标从理解人类语言文本的意义开始,扩展到生成有意义且让人类理解的自然语言句子。NLP 已经广泛应用于构建能够直接从人类获取指令和请求的系统,如聊天机器人。然而,聊天机器人也需要用自然语言进行回应,这是 NLP 的另一个方面。
让我们来研究一些与 NLP 相关的常见术语。
语料库
在学习自然语言处理(NLP)时,你会经常遇到语料库这个术语。通俗来说,语料库是指某个作者的作品集或某个文学体裁的作品集。在 NLP 研究中,语料库的字典定义略有修改,可以表述为一组书面文本文件,这些文本可以根据选择的任何标准进行分类。这些标准可能包括作者、出版商、体裁、写作类型、时间范围以及与书面文本相关的其他特征。
例如,一部莎士比亚的作品集或某个论坛上关于某个话题的所有帖子都可以被视为语料库。
词类
当我们将一个句子分解成各个单词,并对句子中每个单词对整个句子意义的贡献进行定性分析时,我们就进行了一种确定词类的操作。因此,词类是根据单词在句子中如何贡献句子意义而为单词提供的标注。
在英语中,我们通常有八种词类——动词、名词、代词、形容词、副词、介词、连词和感叹词。
例如,在句子“Ram is reading a book”中,“Ram”是名词且是主语,“reading”是动词且是动作,“book”是名词且是宾语。
你可以在partofspeech.org/
上了解更多关于词类的信息。你也可以尝试在linguakit.com/en/part-of-speech-tagging
上找出你自己句子的词类。
分词
分词是将文档拆解成句子,再将句子拆解成单词的过程。这一点非常重要,因为如果任何计算机程序试图将整个文档作为一个单一字符串进行处理,会变得极为复杂,处理字符串所需的资源也会极其庞大。
此外,通常不需要一次性阅读所有句子就能理解整个文档的含义。通常,每个句子都有其独立的含义,通过统计方法与文档中的其他句子结合,可以确定文档的整体意义和内容。
同样,我们通常需要将句子拆解为单词,以便更好地处理句子,使句子的含义可以从字典中推导出来,其中每个单词都会单独列出。
词干提取和词形还原
词干提取和词形还原是自然语言处理中密切相关的术语,但它们之间有细微但重要的区别。两种方法的目标都是确定任何给定单词的词根,以便将该单词的派生形式与字典中的词根进行匹配。
词干提取是一个基于规则的过程,其中单词会被修剪,有时会附加指示其词根的修饰符。然而,词干提取有时可能会生成在字典中不存在的词根,这样的词根对人类读者来说没有任何意义。
词形还原是将单词转换为其词根或词典中给出的基本形式的过程。因此,单词原本的含义可以从人类字典中推导出来,使得词形还原后的文本比词干提取文本更容易处理。此外,词形还原会考虑单词在句子中的词性,再确定其正确的词根,而词干提取算法则忽略这一点。这使得词形还原比词干提取更能理解上下文。
词袋模型
计算机无法直接处理和处理文本。因此,所有文本在输入到机器学习模型之前必须转换为数字。将文本转换为一组数字的过程,使得在任何时候都可以从转换后的文本中提取出原始文本中最重要的部分,这个过程称为特征提取或编码。词袋模型(BoW)是执行文本特征提取的一个常见且简单的技术。
词袋模型实现的步骤如下:
-
从文档中提取所有唯一单词。
-
创建一个包含文档中所有唯一单词的向量。
-
根据单词向量中的任何单词是否出现在该文档中,将每个文档转换为一个布尔数组。
例如,考虑以下三篇文档:
-
Ram 是一个男孩。
-
Ram 是一个好男孩。
-
Ram 不是女孩。
这些文档中出现的唯一单词可以列在一个向量中,表示为["Ram", "is", "a", "boy", "good", "not", "girl"]。
因此,每个句子可以转换如下:
-
[1, 1, 1, 1, 0, 0, 0]
-
[1, 1, 1, 1, 1, 0, 0]
-
[1, 1, 1, 0, 0, 1, 1]
你会发现,BoW 方法往往会丢失每个词语在句子中出现的位置或它对句子的贡献。因此,BoW 是一个非常基础的特征提取方法,可能不适用于那些需要上下文感知的应用。
相似度
相似度是衡量两句给定句子相似程度的标准。它是计算机科学领域中一个非常常见的操作,适用于所有需要维护记录的地方,如搜索正确文档、在文档中查找单词、身份验证及其他应用。
计算两个文档之间相似度的方法有很多。Jaccard 指数是最基本的形式之一,它基于两个文档中相同的标记数量占文档中所有唯一标记总数的百分比来计算相似度。
余弦相似度是另一种非常流行的相似度指标,它通过计算将两个文档转化为向量后,两个向量之间形成的余弦角度来计算相似度,通常通过 BoW 或其他特征提取技术进行向量化。
牢记这些概念后,让我们继续研究聊天机器人,它们是自然语言处理应用中最流行的形式之一。
聊天机器人简介
聊天机器人是自然语言处理(NLP)应用的一部分,专门处理对话界面。这些界面也可以扩展其功能,处理基础的命令和动作,这时它们被称为基于语音的虚拟助手。随着 Google Home 和 Amazon 的 Alexa 等专用设备的推出,基于语音的虚拟助手最近得到了快速发展。
聊天机器人可以以多种形式存在。它们不需要仅仅作为虚拟助手出现。你可以在游戏中与聊天机器人互动,它会试图将故事情节引导向某个方向,或者你也可以与一些公司在社交媒体平台(如 Twitter 或 Facebook)上使用的社交聊天机器人互动,回复客户的消息。聊天机器人可以被视为互动语音响应(IVR)系统的升级,具备了更高的智能和应对未知输入的能力,有时仅仅是回复一个默认的答复,有时则能根据提供的输入进行精确的计算性回应。
虚拟助手也可以存在于网站上,提供指导并为访问者提供帮助。这类助手通常出现在网站上,主要为消费者提供即时的查询支持。你一定见过许多网站上,通常位于屏幕右下角的“提问”或“我能帮你吗”聊天框。这些网站通常会用自动化的聊天机器人代替真人来回答查询。只有在查询过于复杂,无法通过自动化客服聊天机器人回答时,查询才会转交给真人客服。
创建对话式 UI 本身就是一门艺术。你需要使用既清晰又自然的语言。你可以在designguidelines.withgoogle.com/conversation
了解更多关于创建对话式 UI 的内容。
在接下来的部分,我们将创建一个充当客户支持代理的聊天机器人。
创建一个具有客户支持代表个性的 Dialogflow 机器人
Dialogflow 是一个非常流行的工具,用于创建聊天机器人。与 Wit.ai、Botpress、Microsoft Bot Framework 以及其他一些现成的聊天机器人创建服务类似,Dialogflow 的优势在于它与Google Cloud Platform(GCP)的紧密集成,并且可以将 Dialogflow 代理作为 Google Assistant 的动作,Google Assistant 原生运行在数十亿台 Android 设备上。
Dialogflow 曾用名 Api.ai。自从被 Google 收购后,它更名为 Dialogflow,并在流行度和可扩展性方面得到了提升。该平台允许与多个平台进行非常容易的集成,如 Facebook Messenger、Telegram、Slack、Line、Viber 等多个主要通信平台。
我们将在本章中开发的项目将遵循以下架构图:
我们将使用一些在前面的图示中未提及的库和服务。我们会在项目过程中介绍这些库和服务,并讨论为什么了解它们对我们来说很有趣。
开始使用 Dialogflow
要开始使用 Dialogflow,你应该访问其官方网站dialogflow.com
,进入首页,页面上显示产品信息并链接到文档。学习你想要掌握的任何产品或服务的文档总是一个好主意,因为它包含了软件的全部工作原理和功能。我们将在本章的后续部分引用文档中的一些章节。
你可以在cloud.google.com/dialogflow/docs/
找到 Dialogflow 的文档。
由于 Dialogflow 与 GCP 紧密集成,因此我们必须首先创建一个 Google 帐户。为此,请访问account.google.com
创建帐户。如果你是第一次使用自己的 Google 帐户与 Dialogflow 配合使用,可能需要提供一些权限。
让我们继续探讨如何了解 Dialogflow 帐户创建过程以及 UI 的各个部分。
第一步 – 打开 Dialogflow 控制台
您需要点击页面右上角的“转到控制台”按钮,网址是dialogflow.com
。或者,您可以在浏览器中输入dialogflow.cloud.google.com/
。如果您是第一次使用,您将看到如下屏幕:
仪表盘提示您创建一个新代理。
第 2 步 – 创建新代理
现在,我们将创建一个 Dialogflow 代理。在 Dialogflow 中,代理是聊天机器人的另一个名称。正是这个代理接收、处理并响应用户提供的所有输入。
点击“创建代理”按钮,并根据自己的需求填写代理的相关信息,包括代理的名称、默认语言、时区和 Google 项目名称。
如果在这一步之前您没有使用过 GCP,您需要创建一个项目。我们在第六章《使用 Python 在 Google Cloud Platform 上进行深度学习》中讨论过创建 GCP 项目。或者,您也可以在创建代理时让 GCP 自动为您创建一个新项目。
第 3 步 – 了解仪表盘
在成功创建 Dialogflow 代理后,您将看到如下截图中的仪表盘:
在左侧,您可以看到包含构成聊天机器人的各种组件的菜单。这个菜单非常有用,您应该仔细查看其中的所有内容,确保您理解我们在菜单项中所提到的内容。当我们使用诸如“点击实体”的句子时,我们指的是让您点击菜单中的“实体”项。
中间部分将根据菜单中点击的组件而显示不同的内容。默认情况下,当您打开 Dialogflow 控制台时,它会显示聊天机器人的意图列表。什么是意图?
意图是用户通过任何对聊天机器人的话语想要执行的操作。例如,当用户说给我一杯咖啡
时,他们的意图是让聊天机器人“拿来咖啡”:
在最右侧,提供了一个面板,可以随时测试聊天机器人的响应。您可以输入任何文本,测试聊天机器人的响应,系统将提供一堆信息和聊天机器人生成的响应。
请考虑以下测试输入和响应:
当用户输入我的订单状态是什么
时,聊天机器人会询问订单 ID。这个请求会与CheckOrderStatus
意图匹配,并需要一个名为OrderId
的参数。我们将在本项目中定期使用此控制台来调试聊天机器人。
在之前的截图中,我们向您展示了一个预配置的代理及其意图,而您新创建的代理此时还没有任何自定义意图。让我们来创建它们吧!
第 4 步 – 创建意图
现在,我们来创建两个意图。一个意图将为用户提供帮助,另一个则会检查用户提供的订单 ID 的状态。
第 4.1 步 – 创建 HelpIntent
在此子步骤中,点击左侧菜单中“Intents”项右侧的 + 按钮。您将看到一个空白的意图创建表单。
在意图创建表单中,您将能够看到以下标题:
对于此意图,填写意图名称为HelpIntent
。
现在,请按照接下来的步骤完成此意图的创建。
第 4.1.1 步 – 输入 HelpIntent 的训练短语
现在,我们需要定义可能触发该意图的短语。为此,点击“Training Phrases”标题,并输入一些示例训练短语,如下所示:
每次对意图进行更改时,请确保点击“Save”保存。
第 4.1.2 步 – 添加响应
为了响应用户在该意图中的查询,我们需要定义可能的响应。点击意图创建表单中的“Responses”标题,并添加一个示例响应,如下所示:
保存该意图。一旦我们完成构建它,我们就可以通过输入类似于我们为该意图定义的训练短语的内容来测试聊天机器人。
第 4.1.3 步 – 测试意图
让我们来测试HelpIntent
。在右侧的测试面板中,输入Can you help me?
。代理将产生以下响应:
请注意前一截图底部匹配的意图。由于HelpIntent
成功匹配了输入(该输入并未在训练短语中明确定义),我们可以得出结论,代理工作正常。
为什么代理需要对其未经过训练的输入做出响应呢?这是因为在测试某个特定意图时,我们希望确保任何与训练短语完全匹配或接近匹配的用户输入,都能被该意图正确匹配。如果它没有匹配到与预期意图相关的查询,您需要提供更多的训练短语,并检查是否有任何其他意图中的训练与之冲突。
现在我们已经有了一个意图,告诉用户这个聊天机器人可以做什么——也就是检查订单的状态——接下来我们来创建一个可以实际检查订单状态的意图。
第 4.2 步 – 创建 CheckOrderStatus 意图
点击“Create Intent”按钮,并将意图名称输入为CheckOrderStatus
。
第 4.2.1 步 – 输入 CheckOrderStatus 意图的训练短语
对于此意图,我们输入以下训练短语:
-
订单 ID 12345 的状态是什么?
-
我的产品什么时候到?
-
我的订单发生了什么?
-
我的订单什么时候到?
-
我的订单状态是什么?
请注意,第一个训练短语与其他短语不同,因为它包含了一个订单 ID。
我们需要能够识别它为一个订单 ID,并利用它来获取订单状态。
步骤 4.2.2 – 从输入中提取并保存订单 ID
在CheckOrderStatus
意图的第一个训练短语中,双击 12345,一个菜单会弹出,如下所示:
选择@sys.number,然后输入参数名称为OrderId
。您的训练短语将如下所示:
但有时,就像其他训练短语一样,用户在没有提示的情况下不会提到订单 ID。让我们添加一个提示,并在找到订单 ID 时进行存储。
步骤 4.2.3 – 存储参数并在未找到时进行提示
向下滚动到意图创建表单中的“动作和参数”标题。输入OrderId
作为参数名称和值,并勾选“必填”复选框。以下截图应该类似于当前屏幕上的内容:
在OrderId
参数的右侧,点击“定义提示”以为该参数添加提示。一个示例提示可以是好的,您能告诉我订单 ID 吗?看起来是 12345!
。
我们预期在这个提示之后,用户一定会明确说明订单 ID,然后与该意图的第一个训练短语匹配。
接下来,我们需要为这个意图定义响应。
步骤 4.2.4 – 为 CheckOrderStatus 意图启用通过完成的响应
请记住,此意图需要从获得的订单 ID 中获取订单状态。在这种情况下,一组固定的响应将无法满足目的。因此,我们将利用意图创建表单中的“完成”部分。
向下滚动并为此意图启用 Webhook 的完成方法。此部分现在应该如下所示:
完成允许您的 Dialogflow 代理查询外部 API,以生成代理所需的响应。代理接收到的查询相关的元数据将被发送到外部 API,后者将理解并决定需要给出的响应。这对于通过聊天机器人提供动态响应非常有用。
我们现在必须定义这个 Webhook,用于使用订单 ID 获取订单状态。
步骤 5 – 创建 Webhook
现在我们将创建一个 Webhook,它将在 Firebase 云控制台上运行并调用我们订单管理门户中的外部 API。
点击菜单栏中的 Fulfillment 项目。您将看到启用 webhook 或使用 Firebase 云函数的选项。打开内联编辑器。您的屏幕将类似于以下截图:
我们将自定义内联编辑器中的两个文件。
步骤 6 – 创建 Firebase 云函数
Firebase 云函数在 Firebase 平台上运行,按您在创建 Dialogflow 代理时选择或创建的 GCP 项目的规定计费。您可以在dialogflow.com/docs/how-tos/getting-started-fulfillment
查看更多关于云函数的信息。
步骤 6.1 – 向 package.json 添加所需的包
在内联编辑器中的package.json
文件里,我们将添加request
和request-promise-native
包到依赖项中,如下所示:
"dependencies": {
"actions-on-google": "².2.0",
"firebase-admin": "⁵.13.1",
"firebase-functions": "².0.2",
"dialogflow": "⁰.6.0",
"dialogflow-fulfillment": "⁰.5.0",
"request": "*",
"request-promise-native": "*"
}
这些包将在代理构建时自动获取,因此您无需显式执行任何命令来安装它们。
步骤 6.2 – 向 index.js 添加逻辑
我们将添加调用订单管理系统 API 所需的代码。将以下函数添加到dialogflowFirebaseFulfillment
对象定义中:
function checkOrderStatus(){
const request = require('request-promise-native');
var orderId = agent.parameters.OrderId;
var url = "https://example.com/api/checkOrderStatus/"+orderId;
return request.get(url)
.then(jsonBody => {
var body = JSON.parse(jsonBody);
agent.add("Your order is: " + body.order[0].order_status);
return Promise.resolve(agent);
})
.catch(err => {
agent.add('Unable to get result');
return Promise.resolve(agent);
});
}
在文件末尾,在dialogflowFirebaseFulfillment
对象定义结束之前,添加您之前创建的函数与 Dialogflow 代理中匹配的意图之间的映射,然后调用 webhook 来生成响应:
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('CheckOrderStatus', checkOrderStatus);
agent.handleRequest(intentMap);
现在,点击部署(Deploy)按钮,部署该函数。您将在屏幕右下角看到部署状态通知。等待部署和构建完成。
步骤 7 – 为机器人添加个性
为机器人添加个性更多的是关于您如何选择回应方式,以及如何通过代理中的回应和提示来推动对话。
例如,在之前的示例中,我们为用户的输入选择了一个非常标准的响应,但我们完全可以通过使用更贴近实际的语言或其他装饰元素让其变得更加有趣。如果我们不直接显示响应获取 API 的输出,而是加入对话装饰元素,比如太好了,现在让我看看您的订单进度...
,并且在获取和加载响应时,让 Fulfillment 函数生成如快到了...
、快要到了...
、嗯,让我看看...
等对话填充词,依照具体情况的需求,这样看起来会非常真实。
您还可以通过 Dialogflow 的小对话模块为聊天机器人设置一些有趣的琐事。要使用它,点击左侧的小对话菜单项并启用小对话。您可以添加一些有趣的响应,当您的机器人接收到特定查询时,会做出回应,如下所示:
小对话非常有助于为你的聊天机器人添加独特的个性!
在接下来的步骤中,我们将创建一个 UI,以便直接从订单管理网站与这个聊天机器人进行互动。然而,由于我们谈论的是基于 REST API 的接口,我们很可能会将这个 UI 与我们为订单管理系统创建的 API 分开托管。
这个云函数调用了一个你需要创建的 HTTPS API。在下一部分,我们将学习如何创建一个能够处理本地机器上 HTTPS 请求的 API。
使用 ngrok 来便捷地在 localhost 上实现 HTTPS API
你需要为云函数脚本创建你自己的订单管理系统 API,以便它能够从 API 获取订单状态。你可以在tiny.cc/omsapi
找到一个快速示例。你的 API 必须在 HTTPS URL 上运行。为了实现这一点,你可以使用诸如 PythonAnywhere 和 ngrok 等服务。PythonAnywhere 将在其服务器上托管你的代码并提供固定的 URL,而 ngrok 可以安装并在本地运行,为localhost
提供转发地址。
假设你需要在系统的8000
端口运行 Django 项目用于订单管理 API,并希望提供一个 HTTPS URL 进行测试;你可以通过以下步骤使用 ngrok 轻松实现:
- 下载 ngrok 工具。
首先,访问ngrok.com
,点击顶部导航菜单中的下载按钮。根据需要选择合适版本的工具并下载到你的系统。
- 创建一个账户。
接下来,在网站上注册一个账户并进入仪表板。你可以使用 GitHub 或 Google 认证来快速设置账户。
你将看到如下仪表板:
由于你已经下载并安装了工具,你可以直接跳到连接账户的步骤。
- 将你的 ngrok 账户与工具连接。
复制 ngrok 仪表板中Connect your account部分给出的命令——它包含你账户的 authtoken,运行后将 ngrok 工具与你系统中的 ngrok 账户连接。
然后,我们准备好继续处理localhost
端口。
- 设置 ngrok 地址以转发到
localhost
。
最后,使用以下命令开始将所有发送到随机生成的 ngrok URL 的请求转发到localhost
:
ngrok http 8000
ngrok 服务启动后会保持活跃,只要你保持终端开启。你应该会在屏幕上看到类似于以下截图的输出:
所有发送到你的 ngrok URL 的请求都会在终端中记录。你可以在请求日志上方的表格中的Forwarding
行找到你的 ngrok URL。注意,http
和https
端口都会被转发。你现在可以使用在本地机器上运行的 API 服务,从 Firebase 发起调用,而 Firebase 只允许 HTTPS 调用。
使用 Django 创建测试 UI 来管理订单
在本书中,我们之前使用过 Django,具体见 第八章,在 Microsoft Azure 上使用 Python 进行深度学习,以及 第十章,使用深度学习保护 Web 应用程序。因此,我们将跳过 Django 如何工作的细节以及如何开始使用它。让我们直接进入创建一个可以通过语音交互的 UI!
如果你还没有在系统上安装 Django,请参考 第八章,在 Microsoft Azure 上使用 Python 进行深度学习 中的 Django Web 开发简介 部分。
第一步 – 创建一个 Django 项目
每个 Django 网站都是一个项目。要创建一个项目,请使用此命令:
django-admin startproject ordersui
创建一个名为 ordersui
的目录,目录结构如下:
ordersui/
| -- ordersui/
| __init.py__
| settings.py
| urls.py
| wsgi.py
| -- manage.py
让我们继续创建该项目的模块。
第二步 – 创建一个使用订单管理系统 API 的应用
请记住,每个 Django 项目由多个 Django 应用协同工作。现在我们将在这个项目中创建一个 Django 应用,该应用将使用订单管理系统的 API,并提供一个 UI 以查看 API 数据库中的内容。这对验证 Dialogflow 代理是否正常工作非常重要。
在新的终端或命令提示符中使用 cd
命令切换到 ordersui
目录。然后,使用以下命令创建一个应用:
python manage.py startapp apiui
这将在 ordersui
Django 项目的应用目录中创建一个目录,结构如下:
apiui/
| -- __init__.py
| -- admin.py
| -- apps.py
| -- migrations/
| __init__.py
| -- models.py
| -- tests.py
| -- views.py
在开始开发模块之前,让我们在下一节中定义一些项目级别的设置。
第三步 – 设置 settings.py
我们现在将进行一些配置,这些配置在 ordersui/settings.py
文件中是必需的。
第三步.1 – 将 apiui 应用添加到已安装应用列表中
在 INSTALLED_APPS
列表中,添加 apiui
应用,如下所示:
# Application definition
INSTALLED_APPS = [
'apiui',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Django 框架只会在运行时包括 INSTALLED_APPS
指令中列出的应用,如前面的代码所示。我们还需要定义项目的数据库连接,具体内容将在下一节中展示。
第三步.2 – 移除数据库设置
我们将删除数据库连接设置配置,因为在这个 UI 中我们不需要数据库连接。
注释掉 DATABASES
字典,如下所示:
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
保存文件。完成此操作后,我们将设置一个 URL 路由指向 apiui
路由。
第四步 – 向 apiui 添加路由
更改 ordersui/urls.py
中的代码,添加路径以包含 apiui
应用中的路由设置文件。你的文件将包含以下代码:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('apiui.urls')),
]
保存文件。在项目级别设置完路由后,我们还需要在模块级别设置路由,正如我们将在下一节中做的那样。
第五步 – 在 apiui 应用中添加路由
现在我们已经将项目配置为使用apiui
URL 路由,接下来我们需要为这个应用创建所需的文件。在apiui
目录下创建一个名为urls.py
的文件,并添加以下内容:
from django.urls import path
from . import views
urlpatterns = [
path('', views.indexView, name='indexView'),
path('<int:orderId>', views.viewOrder, name='viewOrder'),
]
保存文件。现在我们已经指定了应用中可用的路由,接下来我们需要为每个路由创建视图,正如我们在下一部分将要做的那样。
第 6 步 – 创建所需的视图
在我们创建的路由中,我们提到了两个视图——indexView
,它不接受任何参数,以及viewOrder
,它接受一个名为orderId
的参数。在apiui
目录中创建一个名为views.py
的新文件,并按照下一步操作来创建所需的视图。
第 6.1 步 – 创建 indexView
该路由将简单地显示订单管理系统中的已下订单。我们使用以下代码:
from django.shortcuts import render, redirect
from django.contrib import messages
import requests
def indexView(request):
URL = "https://example.com/api/"
r = requests.get(url=URL)
data = r.json()
return render(request, 'index.html', context={'orders': data['orders']})
我们将在接下来的部分中创建viewOrder
视图。
第 6.2 步 – 创建 viewOrder
如果我们将一个订单 ID 作为/orderId
传递给相同的/
路由,那么我们应该返回该订单的状态。使用以下代码:
def viewOrder(request, orderId):
URL = "https://example.com/api/" + str(orderId)
r = requests.get(url=URL)
data = r.json()
return render(request, 'view.html', {'order': data['order']})
我们已经完成了创建该项目所需的不同视图;然而,我们还没有创建它们所呈现的模板。让我们在接下来的部分中创建所需的模板。
第 7 步 – 创建模板
在我们之前定义的视图中,我们使用了两个模板——index.html
和view.html
。但为了使它们与设计保持一致,我们还需要设置一个base.html
模板,它将作为 UI 中其他视图模板的主模板。
由于这些模板大多数只是 HTML 模板,对网站的核心内容影响不大,我们已将这些文件的代码提供在tiny.cc/ordersui-templates
上。你需要将模板文件保存在apiui
目录下名为templates
的文件夹中。
在这个阶段,你将能够启动 Django 项目服务器,并通过以下命令在浏览器中查看网站:
python manage.py runserver
现在我们的服务器正在运行,我们将在下一部分围绕它创建一个语音接口。
使用 Web 语音 API 在网页上实现语音识别和语音合成
在 Web 开发领域,最近一个非常令人兴奋的发展是 Web 语音 API 的推出。虽然 Google 已经在 Google Chrome 浏览器(桌面版和 Android 版)中全面支持 Web 语音 API,但 Safari 和 Firefox 仅提供了部分实现。Web 语音 API 主要由两个组件构成:
-
语音合成:更广为人知的是TTS。它的功能是为任何给定的文本生成语音讲解。
-
语音识别:也称为STT。它的功能是识别用户所说的词并将其转换为相应的文本。
你可以阅读 Web Speech API 的详细文档,文档可在 Mozilla 的文档页面找到(tiny.cc/webspeech-moz
)。你还可以看到 Google 提供的技术演示,链接在此:tiny.cc/webspeech-demo
:
在接下来的步骤中,我们将向网站 UI 添加一个基于 Web Speech API 的提问按钮。
步骤 1 – 创建按钮元素
本节中的所有代码必须放入 UI 的 base.html
模板中,以便在网站的所有页面中都能使用。
我们使用以下代码快速创建一个提问按钮文本,该按钮将位于网站的右下角:
<div id="customerChatRoot" class="btn btn-warning" style="position: fixed; bottom: 32px; right: 32px;">Ask a question</div>
现在,我们需要初始化并配置 Web Speech API,接下来我们将在下一节中进行此操作。
步骤 2 – 初始化 Web Speech API 并进行配置
当网页加载完成时,我们需要初始化 Web Speech API 对象,并为其设置必要的配置。为此,请使用以下代码:
$(document).ready(function(){
window.SpeechRecognition = window.webkitSpeechRecognition || window.SpeechRecognition;
var finalTranscript = '';
var recognition = new window.SpeechRecognition();
recognition.interimResults = false;
recognition.maxAlternatives = 10;
recognition.continuous = true;
recognition.onresult = (event) => {
// define success content here
}
// click handler for button here
});
你可以看到,我们初始化了一个 Web SpeechRecognition
API 对象,并对其进行了某些配置。让我们试着理解这些配置:
-
recognition.interimResults
(布尔值)指示 API 是否应尝试识别暂时的结果或尚未说出的单词。这会给我们的用例增加额外开销,因此已关闭。当需要转录速度而非转录准确性时(例如生成实时转录以记录某人讲话内容),开启此功能会更有益。 -
recognition.maxAlternatives
(数字)告诉浏览器同一语音片段可以生成多少个备选项。这在浏览器无法非常清楚地识别所说内容时非常有用,用户可以选择正确的识别结果。 -
recognition.continuous
(布尔值)告诉浏览器是否需要持续捕捉音频,或者是否在识别一次语音后停止。
然而,我们尚未定义在执行语音转文本(STT)后接收到结果时执行的代码。我们通过向 recognition.onresult
函数添加代码来实现这一点,如下所示:
let interimTranscript = '';
for (let i = event.resultIndex, len = event.results.length; i < len; i++) {
let transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript;
} else {
interimTranscript += transcript;
}
}
goDialogFlow(finalTranscript);
finalTranscript = '';
上述代码块在用户讲话时创建了一个临时的转录内容,并在更多单词被说出时不断更新。当用户停止讲话时,临时转录内容将附加到最终转录内容中,并传递给处理与 Dialogflow 交互的函数。在从 Dialogflow 代理收到响应后,最终转录内容将重置,以准备接受下一个来自用户的语音输入。
请注意,我们已将用户语音的最终识别转录发送到名为 goDialogFlow()
的函数。让我们定义这个函数。
步骤 3 – 调用 Dialogflow 代理
一旦我们获取到用户语音查询的文本版本,我们将把它发送到 Dialogflow 代理,如下所示:
function goDialogFlow(text){
$.ajax({
type: "POST",
url: "https://XXXXXXXX.gateway.dialogflow.cloud.ushakov.co",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"session": "test",
"queryInput": {
"text": {
"text": text,
"languageCode": "en"
}
}
}),
success: function(data) {
var res = data.queryResult.fulfillmentText;
speechSynthesis.speak(new SpeechSynthesisUtterance(res));
},
error: function() {
console.log("Internal Server Error");
}
});
}
你会注意到,当 API 调用成功时,我们使用 SpeechSynthesis API 将结果读出给用户。它的使用比 SpeechRecognition API 更加简单,因此它是第一个出现在 Firefox 和 Safari 浏览器上的。
请注意前面函数中使用的 API URL。它现在看起来可能有点奇怪,你可能会想知道我们是从哪里获得这个 URL 的。我们所做的实际上是跳过了使用终端设置 Dialogflow 代理服务账号配置的要求,因为这些配置通常是本地系统特有的,且很难移植。
要为你的项目获取类似的 URL,请按照以下步骤操作;否则,可以跳过 步骤 4,直接进入 步骤 5。
步骤 4 – 在 Dialogflow Gateway 上创建 Dialogflow API 代理
前往 dialogflow.cloud.ushakov.co/
。你将看到如下界面:
Dialogflow Gateway 促进了你的语音用户界面与 Dialogflow 代理之间的互动。在我们的项目作为静态网站托管时,这非常有用。Dialogflow Gateway 为 Dialogflow API 提供了简化的 API 封装,使用起来非常简单。
你需要创建一个账户才能开始使用 Dialogflow,如下节所示。
步骤 4.1 – 在 Dialogflow Gateway 上创建账户
点击“开始使用”以开始平台上的账户创建过程。系统会要求你使用 Google 账号登录。确保使用的是你之前创建 Dialogflow 代理时使用的同一账号。
步骤 4.2 – 为你的 Dialogflow 代理项目创建服务账号
我们之前在 第六章《使用 Python 在 Google Cloud Platform 上进行深度学习》中详细讨论了如何为 GCP 项目创建服务账号。为与你的 Dialogflow 代理关联的项目创建一个新的服务密钥,如下所示:
一旦密钥成功创建,一个对话框会弹出,告诉你密钥已保存在计算机上,如下所示:
服务账号凭据以 JSON 格式下载到本地系统,文件名如前截图所示。
现在,我们将使用这个服务账号凭据文件将 Dialogflow Gateway 连接到我们的 Dialogflow 代理。
步骤 4.3 – 上传服务密钥文件到 Dialogflow Gateway
在 Dialogflow Gateway 控制台上,你会看到“上传密钥”按钮。点击它上传你生成的服务账号密钥文件。上传完成后,控制台将显示你的 Dialogflow API 代理 URL,如下所示:
我们将在之前定义的函数中使用 Gateway URL。
第五步——为按钮添加点击处理程序
最后,我们为“Ask a question”按钮添加一个 click
处理程序,使其能够触发用户输入的语音识别,并从 Dialogflow 代理合成输出。
在 步骤 2 中定义的 ready
函数内,添加以下 click
处理程序代码:
$('#customerChatRoot').click(function(){
recognition.start();
$(this).text('Speak!');
});
现在,当麦克风开始监听用户输入时,按钮文本会变成“Speak!”,提示用户开始讲话。
尝试在您的设置上测试网站,看看它能准确地工作到什么程度!
总结
在本章中,我们结合了几种技术,提出了一个端到端的项目,展示了将深度学习应用于网站的快速发展的一个方面。我们涉及了诸如 Dialogflow、Dialogflow Gateway、GCP IAM、Firebase Cloud Functions 和 ngrok 等工具。我们还展示了如何构建一个基于 REST API 的 UI,以及如何使用 Web Speech API 使其可访问。Web Speech API 尽管目前处于初期阶段,但它是一个用于 Web 浏览器的前沿技术,预计在未来几年将快速发展。
可以肯定地说,深度学习在网络上的应用具有巨大的潜力,并将在许多即将到来的企业成功中起到关键作用。在下一章中,我们将探讨深度学习在 Web 开发中的一些热门研究领域,并讨论如何规划最佳的进展方式。
附录:网络上深度学习的成功故事与新兴领域
了解他人如何使用任何技术通常是很重要的,这样可以了解其应用的规模及其所能承诺的投资回报。本章阐述了一些最著名的网站,它们的产品在很大程度上依赖于深度学习的强大功能。本章还讨论了一些可以通过深度学习提升的网页开发关键研究领域。本章将帮助您更深入地了解网络技术与深度学习的融合,并激励您创造自己的智能网页应用。
本章分为两个主要部分:
-
一些成功的案例,诸如 Quora 和 Duolingo 等组织,它们已经将深度学习应用于其产品
-
深度学习中的一些关键新兴领域,如阅读理解、音频搜索等
让我们开始吧!
成功案例
在这一部分,我们将简要回顾一些在其核心使用人工智能(AI)来推动业务增长的产品/公司。在这里值得注意的是,您的整个产品或服务是否基于任何 AI 技术或算法并不重要;只要在某个小部分或某个特定功能上使用了 AI,就足以提升产品的实用性,从而促使客户广泛使用您的产品。有时,您的产品可能根本没有任何 AI 功能,而是仅仅使用它来进行数据分析,提出预期的趋势,以确保您的产品符合未来的趋势。让我们看看这些公司是如何通过这些方法取得成功的。
Quora
在 Quora 之前,已经存在了大量的问答网站和论坛。在互联网历史的某个时期,在线论坛被认为是无法再改进的东西;然而,Quora 通过一些利用深度学习的调整,使其能够迅速超越其他论坛。以下是它们实施的一些调整:
-
他们启用了“提问即答”功能,允许贡献者在问题发布后立即请求答案。这使得问题更容易接触到相关的学科专家,专家们迅速回答问题,提升了平台的响应速度和准确性。
-
他们通过自然语言处理(NLP)屏蔽掉了那些写得不好的问题和答案。这引入了自动化管理的论坛概念,使内容更加优质。
-
对于任何给定的问答线程,标签和相关文章的确定使得发现相似问题变得容易。这使得 Quora 用户花费大量时间阅读与自己问题相似的问题的答案,只为了从每一个答案中找到新的信息。
-
Quora Digest 是一个高度策划的文章集合,基于用户兴趣,几乎总是能够成功吸引用户重新回到平台:
Quora 曾一度成为(并且至今仍然是)互联网上最具吸引力的社交平台之一。它们将一个简单的问答网站,通过深度学习,转变成了一个令人惊叹的平台。你可以通过 quora.com
访问这个平台。
Duolingo
学习新语言一直是一项艰难的任务。2012 年 Duolingo 登场时,带来了一个开始变得越来越重要的术语——人工智能。它们将像记忆单词和语法规则这样枯燥的任务,转化为针对每个用户有不同反馈的迷你游戏。Duolingo 的人工智能考虑到了人类思维的时间性。他们研究了一个人学习的单词,可能会多快忘记的情况。他们称这一概念为半衰期回归,并利用这一概念加强用户可能会在任何时刻遗忘的单词的记忆。
这一点对 Duolingo 非常有利,使其成为移动应用商店中最受欢迎的应用之一。他们的网站也是非传统设计的经典例子,并且得到了良好的反馈。你可以通过 duolingo.com
了解更多关于 Duolingo 的信息。
Spotify
音频播放器早已存在了很长时间,但没有人能比得上 Spotify 带来的体验。Spotify 使用深度学习来确定用户在任何时刻想听的歌曲。多年来,他们的人工智能取得了飞跃发展,根据用户最近播放的歌曲,推荐完整的播放列表。Spotify 的迅速崛起激发了大量试图模仿并赶超其人气的产品。
Spotify 还推出了一个非常强大的功能——基于音频样本搜索歌曲。这一功能迅速成为热门,许多用户正是因为无法记住一首喜欢的歌名而下载了 Spotify,只希望能快速找到那首歌的名字。你只需要录制附近播放的歌曲的音频,然后将其输入 Spotify,就能知道那首歌的名字。
Google 搜索/照片
虽然云端图像存储是 Dropbox、Google Photos 等公司早已提供的解决方案,但 Google Photos 通过引入 AI(人工智能)彻底改变了云端图像存储领域。由于其惊人的功能,Google Photos 被全球数十亿人采用,以下是其中一些特色:
-
人脸识别: 这个功能早在 Google 的另一个产品 Picasa 中就已出现,Picasa 被认为是 Google Photos 的前身。
-
向导: Google Photos 会自动确定哪些照片是在同一事件或场合拍摄的。然后,它尝试创建有关相关图片的电影或简单地触及图片,使它们看起来更好。有时,Google Photos 还会使用看起来处于一系列中的照片创建动画 GIF。
-
文档和表情包的识别: Google Photos 建议其用户归档旧文档、截图和表情包。这在节省设备存储方面非常有帮助:
Google Photos 因其在幕后使用深度学习而成为个人在线画廊的市场领导者。如果你想了解更多,请访问photos.google.com
。
在这一部分,我们看了一些深度学习极大影响的产品。在下一部分,我们将看到一些深度学习似乎带来了许多积极结果的新兴领域。
关键新兴领域
在前面的几节中,我们看到了几家公司如何整合基于深度学习的技术以改进其产品。在本节中,我们将讨论一些当前正在进行密集研究的领域,并通过 Web 开发的视角看到它们的影响力。
音频搜索
假设你在酒吧里,你喜欢现场乐队演奏的歌曲。在你的脑海中,你知道你以前听过那首歌,但是你想不起歌名。如果有一个系统能够听歌并搜索它的名字,那不是很棒吗?欢迎来到音频搜索引擎的世界!
目前有许多现有的音频搜索引擎可用,其中由 Google Assistant 提供的 Sound Search(声音搜索)是其中最受欢迎的之一。你也许还想看看 Shazam。在以下截图中,你可以看到通过 Sound Search 产生的一个音频搜索结果示例:
要使系统根据接收到的音频信号执行音频搜索,系统首先需要处理信号,这被称为音频信号处理。然后,系统将该处理后的信号与其现有的数以万计的歌曲数据库进行比较。在信号甚至与现有数据库进行比较之前,它会使用神经网络给出特定的表示,这通常被称为指纹;然而,这仍然是一个活跃的研究领域,我强烈建议你阅读文章ai.googleblog.com/2018/09/googles-next-generation-music.html
以获取这些技术的更详细概述。
阅读理解
你是否曾希望搜索引擎能直接给出搜索查询的答案,而不是寻找可能包含答案的相关链接?好吧,如果系统具备阅读理解能力,现在已经有可能实现这一目标。我们来看一下下面的截图,看看这是什么意思:
如果你仔细观察,我们甚至没有将声明 Sachin Tendulkar's father 以问题的形式表达。现代系统足够强大,可以自行推断出这样的属性。
现在,为了更好地理解具备阅读理解能力的系统(或机器)的深度,假设你在进行网络搜索后想找到问题的答案。这是你需要经过的多步骤过程:
-
你从相关关键词出发,制定搜索查询,搜索引擎进行搜索。
-
然后,搜索引擎会给出一份与给定搜索查询相关的文档列表。
-
你浏览这些文档,根据你的理解整理其中的信息,然后得出结论。
仍然有一些步骤是人工的,问题依然存在——我们能否设计出一个系统,自动化地为我们找到合适的答案?现有的搜索引擎会为给定的搜索查询提供相关文档的列表,但不足以开发出能够自动生成答案的系统。简而言之,这样的系统需要做到以下几点:
-
遵循相关文档的结构。
-
从这些文档中提取出有意义的内容。
-
得出最终答案。
让我们稍微简化一下这个问题。假设对于一个给定的问题,我们已经有了一份相关段落的列表,现在我们需要开发一个系统,使其能够从这些段落中提取有意义的信息,并给出这个问题的明确答案。在阅读理解系统中,神经网络通常学习捕捉给定问题和相关段落之间的深层语义关系,然后生成最终答案。
正如你可能已经发现的,像 Google 搜索、Bing 等搜索引擎已经具备了阅读理解能力。
社交媒体上的假新闻检测
随着社交媒体的迅速崛起,新闻从未匮乏。社交媒体已经轻松地成为我们获取新闻的主要来源之一;然而,它的真实性往往得不到保证。在社交媒体上你遇到的并非每一篇新闻都是可信的,可以安全地说,绝大多数新闻都是假的。这一现象的后果可能非常严重,确实可能引发滥用、暴力等行为。
有一些组织和机构正在努力应对这一问题,并让人们意识到新闻文章的真实性。考虑到我们每天在社交媒体上看到的新闻量,这项任务可能非常繁琐。那么,问题变成了,我们能否利用机器学习的力量自动检测虚假新闻?事实上,这是一个活跃的研究领域,目前尚未出现已知的能大规模解决这一问题的实际应用。
然而,以下是一些不同团队进行的研究,他们使用了经典的机器学习和深度学习方法:
-
社交媒体网络中的虚假新闻检测:
www.sciencedirect.com/science/article/pii/S1877050918318210
-
使用几何深度学习的社交媒体虚假新闻检测:
arxiv.org/abs/1902.06673
我们鼓励你查看这篇调查论文:arxiv.org/pdf/1812.00315.pdf
,它提供了关于各种虚假新闻检测技术的全面指南,并讨论了相关研究。另一方面,一家名为 Varia 的德国初创公司(www.varia.media/
)正在以独特的方式解决虚假新闻问题。他们并不是直接检测新闻的真实性,而是提供某些新闻项目的不同视角。换句话说,他们把“视角”作为一种服务提供。如果你想了解更多,绝对应该去看看:alpha.varia.media/
。
总结
在本书的最后一章中,我们尝试激励你构建下一个深度学习项目,并在网络平台上应用它。你可能对更多此类公司如何通过人工智能转型并主导市场的故事感兴趣。如果你仔细看看你访问的几乎每个网站,你会发现它们都以某种方式使用了人工智能和深度学习元素,无论是推荐系统还是广告(广告本质上也是一种推广推荐系统)。随后,我们讨论了深度学习领域即将出现的主题,这些主题在不久的将来将在网站上得到实现。如果你能基于这些主题中的任何一个想出一项服务,那将是非常棒的!