谷歌云平台机器学习实用指南-二-

70 阅读1小时+

谷歌云平台机器学习实用指南(二)

原文:annas-archive.org/md5/0c231e89c375d109731af45617713934

译者:飞龙

协议:CC BY-NC-SA 4.0

第六章:机器学习基础知识

到目前为止,在之前的章节中,我们介绍了 GCP 中可用的各种 ETL 流程。在本章中,我们将通过以下主题开始我们的机器学习和深度学习之旅:

  • 机器学习应用

  • 监督学习和无监督学习

  • 主要机器学习技术的概述

  • 数据拆分

  • 测量模型的准确性

  • 机器学习和深度学习之间的区别

  • 深度学习应用

机器学习应用

机器学习涵盖了一系列从历史数据中学习的技巧。基于从历史数据中学习到的模式,机器学习技术预测未来数据集中事件发生的概率。鉴于机器学习的工作方式,这一系列技巧有多个应用。以下几节中,我们将探讨其中的一些。

金融服务业

在金融领域的某些应用如下:

  • 识别贷款/信用卡申请人的风险程度

  • 估计给定客户的信用额度

  • 预测卡交易是否为欺诈交易

  • 识别需要针对活动进行定位的客户细分市场

  • 预测客户在接下来的几个月内可能违约的可能性

  • 推荐客户应该购买的正确金融产品

零售行业

以下是一些机器学习不同技巧在零售行业中的应用:

  • 预测客户可能购买的下一种产品

  • 估计给定产品的最佳价格点

  • 预测产品随时间推移将销售的单元数量

  • 通过捆绑产品进行促销以定位客户

  • 估计客户终身价值

电信行业

以下是机器学习在电信行业中的几个应用:

  • 预测在通话开始前通话掉话的可能性

  • 预测客户在接下来的几个月内可能流失的可能性

  • 识别可以出售给客户的附加月度使用量

  • 识别不太可能为后付费服务付款的客户

  • 为现场销售人员优化劳动力

监督学习和无监督学习

监督学习构成了旨在构建一个近似函数的模型的一组技巧。该函数接受一组输入变量,这些变量也被称为独立变量,并试图将输入变量映射到输出变量,这些变量也被称为依赖变量或标签。

由于我们知道我们正在尝试预测的标签(或值),对于一组输入变量,该技术变成了一个监督学习问题。

以类似的方式,在无监督学习问题中,我们没有必须预测的输出变量。然而,在无监督学习中,我们试图将数据点分组,以便它们形成逻辑组。

可以通过以下图示获得监督学习和无监督学习的高层次区别:

图片

在前面的图中,监督学习方法可以区分两个类别,如下所示:

图片

在监督学习中,有两个主要目标可以实现:

  • 预测事件发生的概率——分类

  • 估计连续因变量的值——回归

帮助分类的主要方法如下:

  • 逻辑回归

  • 决策树

  • 随机森林

  • 梯度提升

  • 神经网络

除了逻辑回归之外,线性回归也有助于估计连续变量(回归)。

虽然这些技术有助于估计连续变量或预测事件发生的概率(离散变量预测),但无监督学习有助于分组。分组可以是行(这是一种典型的聚类技术)或列(一种降维技术)。行分组的主要方法包括:

  • K-means 聚类

  • 层次聚类

  • 基于密度的聚类

列分组的主要方法包括:

  • 主成分分析

  • t-Distributed Stochastic Neighbor Embedding (t-SNE)

列分组可以识别出数据集中存在的客户(观测值)的段。

列分组可以减少列的数量。当独立变量的数量很高时,这一点很有用。通常情况下,如果出现这种情况,构建模型可能会遇到问题,因为需要估计的权重数量可能很高。此外,在解释模型时也可能出现问题,因为一些独立变量之间可能高度相关。在这种情况下,主成分分析或 t-SNE 很有用,因为我们可以在不丢失太多数据集中现有信息的情况下减少独立变量的数量。

在下一节中,我们将概述所有主要的机器学习算法。

机器学习技术概述

在了解主要机器学习技术概述之前,让我们先看看在回归技术或分类技术中我们想要优化的函数。

回归中的目标函数

在回归练习中,我们估计连续变量的值。在这种情况下,我们的预测可能低于实际值或高于实际值;也就是说,误差值可以是正的也可以是负的。在这种情况下,目标函数转化为最小化数据集中每个观测值实际值和预测值之间差异的平方和。

用数学术语来说,前面的内容可以写成如下形式:

图片

在给定的方程中:

  • SSE 代表平方误差和

  • y 指的是因变量的实际值

  • y' 指的是因变量的估计值

  • ∑ 表示数据集中所有观测值的平方误差之和

给定目标函数,让我们从高层次了解线性回归是如何工作的。

线性回归

在线性回归中,我们假设自变量和因变量之间存在线性关系。线性回归表示如下:

在给定的方程中:

  • Y 是因变量

  • W 是与自变量 X 相关的权重

  • b 是截距值

如果有多个自变量(比如说两个自变量,x1x2),方程如下:

在给定的方程中:

  • w1 是与变量 x1 相关的权重

  • w2 是与变量 x2 相关的权重

典型的线性回归看起来如下,其中 x 轴是自变量,y 轴是因变量:

直线(具有特定的斜率和截距)是线性回归的方程。

注意图中线是使整体平方误差最小的线。

决策树

决策树是一种帮助我们从数据中推导规则的技巧。基于规则的技巧在解释模型如何估计因变量值时非常有帮助。

典型的决策树如下所示:

以下图示解释如下:

  • 根节点:这代表整个总体或样本,并进一步分割成两个或更多的节点。

  • 分割:根据一定规则将节点分割成两个或更多子节点的过程。

  • 决策节点:当一个子节点进一步分割成子节点时,它被称为决策节点

  • 叶节点/终端节点:决策树中的最后一个节点是叶节点或终端节点。

  • 剪枝:当我们移除决策节点的子节点时,这个过程称为剪枝。可以说它是分割过程的相反过程。

  • 分支/子树:整个树的子部分称为分支子树

  • 父节点和子节点:被分割成子节点的节点称为子节点的父节点,而子节点是父节点的子节点。

给定一个因变量和一个自变量值,我们将通过以下数据集了解决策树是如何工作的:

var2response
0.11996
0.3839
0.442229
0.512309
0.75815
0.782295
0.841590

在前面的数据集中,变量 var2 是输入变量,而 response 变量是因变量。

在决策树的第一个步骤中,我们将输入变量从低到高排序,并逐一测试多个规则。

在第一种情况下,所有var2值小于0.3的观测值属于决策树的左节点,其他观测值属于决策树的右节点。

在回归练习中,左节点的预测值是所有属于左节点的观测值的response变量的平均值。同样,右节点的预测值是所有属于右节点的观测值的response的平均值。

给定左节点的预测值和属于右节点的观测值的不同的预测值,可以计算每个左节点和右节点的平方误差。一个可能规则的总体误差是左右节点平方误差的总和。

实施的决策规则是在所有可能的规则中具有最小平方误差的规则。

随机森林

随机森林是决策树的扩展。它是一个森林,因为它是由多个树组合而成的,并且是随机的,因为我们为每个决策树随机采样不同的观测值。

随机森林通过平均每个决策树的预测值(这些决策树在原始数据集的样本上工作)来工作。

通常,随机森林比单个决策树表现更好,因为在其中异常值的影响被减少了(因为在某些样本中,异常值可能没有出现),而在决策树中,异常值肯定会出现(如果原始数据集中包含异常值)。

梯度提升

当随机森林在一个构建多个并行树的框架中工作时,梯度提升采取了一种不同的方法——构建一个深度框架。

梯度提升中的梯度指的是实际值和预测值之间的差异,而提升指的是改进,即在不同迭代中改进误差。

梯度提升也利用了决策树工作的以下方式:

  • 构建决策树以估计因变量

  • 计算误差,即实际值和预测值之间的差异

  • 构建另一个预测误差的决策树

  • 通过考虑前一个决策树的预测误差来更新预测

这样,梯度提升持续构建一个预测前一个决策树误差的决策树,从而在梯度提升中构建了一个基于深度的框架。

神经网络

神经网络提供了一种近似非线性函数的方法。通过在加权输入变量的和上应用激活函数来实现非线性。

神经网络看起来是这样的:

图片

输入层包含输入,隐藏层包含加权输入值的总和,其中每个连接都与一个权重相关。

非线性应用于隐藏层。典型的非线性激活函数可以是 sigmoid、tanh 或修正线性单元。

输出层与每个隐藏单元相关的权重之和有关。通过调整权重以使总的平方误差值最小化,获得每个连接相关的权重的最优值。关于神经网络如何工作的更多细节将在后面的章节中提供。

逻辑回归

如前所述,逻辑回归用于根据输入数据集将预测分类为一类或另一类。逻辑回归使用 sigmoid 函数来获得事件发生的概率。

Sigmoid 曲线看起来是这样的:

图片

注意,当 x 轴的值大于 3 时,输出是一个高概率,而当 x 轴的值小于 3 时,输出是一个非常低的概率。

逻辑回归与线性回归的不同之处在于激活函数的使用。线性回归方程将是 Y = a + b * X,而逻辑回归方程将是:

图片

分类中的目标函数

在回归技术中,我们最小化总的平方误差。然而,在分类技术中,我们最小化总的交叉熵误差。

二元交叉熵误差如下:

图片

在给定的方程中:

  • y 是实际的因变量

  • p 是事件发生的概率

对于分类练习,所有前面的算法都适用;只是目标函数变为交叉熵误差最小化,而不是平方误差。

在决策树的情况下,属于根节点的变量是与其他所有独立变量相比提供最高信息增益的变量。信息增益定义为当树被给定变量分割时,整体熵的改善与未分割时相比。

数据分割

在处理任何机器学习模型时,需要解决的一个关键问题是:一旦在未来的数据集上实施,这个模型能达到多高的准确度?

无法立即回答这个问题。然而,从最终从模型构建中受益的商业团队那里获得认可非常重要。在这种情况下,将数据集分为训练集和测试集非常有用。

训练数据集是用于构建模型的数据。测试数据集是模型未见过的数据集;也就是说,数据点没有被用于构建模型。本质上,可以将测试数据集视为未来可能出现的数据集。因此,我们在测试数据集上看到的准确率可能是模型在未来数据集上的准确率。

通常,在回归中,我们处理的是泛化/过拟合的问题。当模型变得如此复杂以至于它完美地拟合所有数据点时,过拟合问题就会出现——从而产生最小的可能错误率。一个典型的过拟合数据集的例子如下所示:

图片

从图数据集中,我们可以观察到黑色曲线并不完美地拟合所有数据点,而蓝色曲线完美地拟合了这些点,因此它在训练数据点上的错误率最小。

然而,与新的数据集上的曲线相比,直线有更大的可能性具有更好的泛化能力。因此,在实践中,回归/分类是在模型的泛化能力和复杂性之间的一种权衡。

模型的泛化能力越低,未见数据点的错误率就越高。

这种现象可以在以下图表中观察到。随着模型复杂性的增加,未见数据点的错误率持续降低,直到达到一个点,之后又开始增加:

图片

蓝色曲线表示训练数据集上的错误率,红色曲线表示测试数据集上的错误率。

验证数据集用于获取模型的最佳超参数。例如,在随机森林或 GBM 等技术中,构建所需的树的数量或树的深度是一个超参数。随着我们不断改变超参数,未见数据集上的准确率也会发生变化。

然而,我们不能继续改变超参数直到测试数据集的准确率最高,因为在这样的情况下,我们已经看到了实际上的未来数据集(测试数据集)。

在这种情况下,验证数据集非常有用,我们不断在训练数据集上改变超参数,直到我们看到验证数据集上的准确率最高。这样就会形成模型的最佳超参数组合。

测量模型的准确度

评估模型准确性的方法在监督学习和无监督学习之间有所不同。

在典型的线性回归(预测连续值的情况下),有几种方法可以衡量模型的误差。通常,误差是在验证集和测试集上测量的,因为在一个训练集(用于构建模型的集合)上测量误差是有误导性的。因此,误差总是在未用于构建模型的集合上测量的。

绝对误差

绝对误差定义为预测值与实际值之间差异的绝对值。让我们想象以下场景:

实际值预测值误差绝对误差
数据点 11001202020
数据点 210080-2020
总体200200040

在先前的场景中,我们看到总体误差是 0(因为一个误差是+20,另一个是-20)。如果我们假设模型的总体误差为 0,我们就会忽略模型在个别数据点上表现不佳的事实。

因此,为了避免正误差和负误差相互抵消从而产生最小误差的问题,我们考虑模型的绝对误差,在这种情况下是 40;并且绝对误差率是 40/200 = 20%。

均方根误差

解决误差符号不一致问题的另一种方法是平方误差(负数的平方是正数)。之前讨论的场景可以翻译如下:

实际值预测值误差平方误差
数据点 110012020400
数据点 210080-20400
总体2002000800

在这种情况下,总体平方误差是 800,均方根误差是(800/2)的平方根,即 20。

在分类练习的情况下,准确度如下测量:绝对误差和 RMSE 适用于预测连续变量。然而,预测具有离散结果的事件的流程是不同的。离散事件预测以概率的形式发生;也就是说,模型的结果是某个事件发生的概率。在这种情况下,尽管绝对误差和 RMSE 在理论上可以使用,但还有其他相关的指标。

混淆矩阵计算模型预测事件结果的数量,并将其与实际值进行比较,如下所示:

预测欺诈预测非欺诈
实际欺诈真阳性TP假阴性FN
实际非欺诈误报FP真阴性TN

灵敏度或 TP 率或召回率 = TP/(总正数)= TP/(TP+FN) 特异性或 TN 率 = TN/(总负数)= TP/(FP + TN)*精确度或正预测值 = TP/(TP + FP)*准确度 = (TP + TN)/(TP + FN + FP + TN)F1 分数 = 2TP/(2TP + FP + FN)

接收者操作特征ROC)曲线给出了各种截止点的真正例率和假正例率之间的关系。假设模型预测值大于 0.8。我们假设我们应该将预测分类为阳性。这里的 0.8 是截止点。在这里,截止点变得重要,因为模型的预测始终是一个概率数——一个介于 0 和 1 之间的值。因此,分析师需要将自己的判断纳入确定最佳截止点。

ROC 曲线是一个曲线,其中(1-特异性)位于 x 轴上,敏感性位于 y 轴上。曲线是通过改变截止点(决定预测值应该是 1 还是 0)来生成敏感性(1-特异性)的各种组合而绘制的。

在一个理想场景中,数据可以清楚地分割,准确率为 100%,存在一个概率的截止点,在此之后预测值属于一个类别;低于截止点的值属于另一个类别。在这种情况下,对于某些截止点的值,ROC 曲线将仅在 y 轴上,即特异性=1。对于其余长度,曲线将与 x 轴平行。

一个典型的 ROC 曲线看起来是这样的:

图片

ROC 曲线是衡量模型性能相对于随机猜测有多好的一个指标。随机猜测是在 5% 的客户流失的情况下,随机猜测者猜测在每二十个客户中,将有一个被标记为潜在流失者。在这种情况下,随机猜测将在随机标记 20% 的所有客户后捕获 20% 的所有流失者。

模型的预测能力在于尽可能接近 100% 的准确性,即尽可能远离随机猜测。

曲线下的面积AUC)是模型曲线与随机猜测曲线之间面积的一个度量。AUC 越高,模型的预测准确性就越高。

机器学习和深度学习之间的区别

到目前为止,我们已经从高层次上了解了各种机器学习算法是如何工作的。在本节中,我们将了解深度学习与机器学习的区别。

机器学习任务的一个关键属性是输入由分析师或数据科学家提供。通常,特征工程在提高模型准确性方面起着关键作用。此外,如果输入数据集是非结构化的,特征工程就会变得非常复杂。往往,这归结为个人在推导相关特征以构建更准确模型方面的知识。

例如,让我们想象一个场景,在这个场景中,给定一个句子中的单词集合,我们试图预测下一个单词。在这种情况下,传统的机器学习算法工作如下:

  • 对句子中的每个单词进行独热编码

  • 使用 one-hot 编码向量表示单词的输入序列

  • 使用 one-hot 编码向量表示输出单词

  • 通过优化相关损失函数来构建一个模型,以预测给定输入单词集的输出单词向量

虽然上述方法有效,但在构建模型时我们面临三个主要挑战:

  • One-hot 编码向量的维度:

    • 一段文本可能包含数百或数千个独特的单词

    • 高维数据可能会引起多个问题——例如多重共线性以及构建模型所需的时间

  • 输入数据集中缺少单词的顺序

  • 两个单词之间的距离相同,无论这两个单词是否相似:

    • 例如,在一个 one-hot 编码的向量场景中,king 和 prince 之间的距离将与 king 和 cheese 之间的距离相同

在这种情况下,深度学习非常有用。通过使用深度学习中的某些技术(例如 Word2vec),我们能够解决刚才列出的问题中的以下问题:

  • 以一种方式在低维空间中表示每个单词,使得相似的单词具有相似的单词向量,而不相似的单词不具有相似的向量

  • 此外,通过在低维空间(比如说 100 维)中表示一个单词,我们就解决了数据高维的问题

Word2vec 技术有多种变体,例如连续词袋模型和连续跳字模型。

CBOW 模型的架构如下:

注意,输入向量是 one-hot 编码版本(正如我们在典型的机器学习模型中会使用的那样)。隐藏层神经元确保我们将 10,000 维的输入向量表示为 300 维的单词向量。

输出层中的实际值代表周围单词(构成上下文)的 one-hot 编码版本。

深度学习中另一种有助于解决上述问题的技术是 循环神经网络RNN)。RNN 致力于解决传统机器学习在之前场景中面临的单词序列问题。

RNN 提供每个单词向量以预测序列中的下一个单词。关于 RNN 如何工作的更多细节将在另一章中提供。RNN 技术的流行变体包括 长短期记忆LSTM)和 门控循环单元GRU):

上述图表示了一个典型的循环神经网络(RNN),其中 x[(t-1)]x[(t)]x[(t+1)] 代表每个时间段的单词,W 是用于预测下一个单词的前一个单词的权重,而 O[(t)] 是时间 t 的输出。

当需要与序列中很久以前出现的单词相关联的权重很高时,LSTM 非常有用。

Word2vec 和 RNN 的组合,它们是神经网络的不同变体,有助于避免在给定的文本数据中特征工程带来的挑战。

为了巩固我们对机器学习和深度学习之间差异的理解,让我们通过另一个示例:预测图像的标签。

我们将使用一个经典示例——MNIST 数据集(我们将在未来的章节中更多地使用 MNIST)。

MNIST 数据集包含从零到九的各种数字的图像。每个图像的大小为 28 x 28 像素。任务是通过对各种像素值进行分析来预测图像的标签。MNIST 数据集中的一个示例图像如下所示:

传统机器学习解决前面问题的方式如下:

  • 将每个像素视为一个单独的变量;也就是说,我们总共有 784 个变量

  • 对标签列进行 one-hot 编码

  • 预测标签发生的概率

解决前面问题的挑战如下:

  • 模型不会考虑像素的相邻关系

  • 模型不会考虑图像的平移或旋转

例如,当图像被适当地移动时,一个零可能看起来像六,反之亦然。同样,如果所有图像都是使用一个数据集进行训练,该数据集中的所有数字都集中在图像中,但测试数据集有一个图像稍微向右或向左移动,预测很可能是准确的。这是因为模型会给每个像素分配权重。

为了解决前面的问题,一种名为卷积神经网络CNN)的深度学习技术非常有用。CNN 的工作方式是它在区域级别而不是像素级别分配权重。本质上,这形成了卷积神经网络中的卷积部分。通过这种方式,使用深度学习考虑了像素的相邻关系。

同样,图像的平移是通过在 CNN 中使用的一种称为最大池化的技术来考虑的。

CNN 的典型架构如下,其更多细节将在后面的章节中解释:

在前面的图中,输入是我们考虑的图像。conv1是应用卷积于滤波器和输入时的输出。鉴于我们应用了多个滤波器,我们将有多个卷积,pool1是应用池化于卷积输出时的输出。卷积和池化的过程会反复应用,直到我们获得最终的完全连接单元,然后将其连接到输出。

深度学习的应用

在上一节中,我们了解了为什么在某些应用中深度学习比机器学习更出色。让我们来看看深度学习的一些应用:

  • 从一种语言翻译到另一种语言

  • 语音转文本转换

  • 多个行业中的图像分析

  • 识别图像中的文本

  • 图像和音频合成

  • 个性化预测用户可能观看/购买的下部电影/产品

  • 时间序列分析

  • 检测罕见事件

摘要

在本章中,我们了解了监督学习和无监督学习之间的主要区别,并对主要的机器学习算法有了概述。我们还通过文本和图像分析的例子,了解了深度学习算法在哪些领域比传统的机器学习算法更出色。

第七章:Google 机器学习 API

如前一章所示,机器学习被广泛应用于各种应用中。然而,一些应用容易构建,而一些则非常难以构建,尤其是对于不太熟悉机器学习的用户。我们将在本章讨论的一些应用属于难以构建的类别,因为这些应用的机器学习模型构建过程数据密集、资源密集,并且需要该领域的大量知识。

在本章中,我们将介绍谷歌(截至 2018 年 3 月)提供的五个机器学习 API。这些 API 旨在作为 RESTful API 直接使用。对于以下提到的每个服务,我们将展示哪些类型的应用程序可以从中受益,以及如何解释返回的结果:

  • 视觉具有标签检测、OCR、面部检测、情感、标志和地标

  • 语音意味着语音转文本

  • NLP 有实体、情感和 POS

  • 翻译

  • 视频智能

视觉 API

视觉 API 允许我们构建许多与视觉相关的应用程序:

  • 在图像中检测标签

  • 在图像中检测文本

  • 面部检测

  • 情感检测

  • 标志检测

  • 地标检测

在我们深入构建应用程序之前,让我们快速了解它们可能如何构建,以面部情感检测为例。

情感检测的过程包括:

  1. 收集大量图像

  2. 使用图像中可能表示的情感人工标记图像

  3. 训练一个卷积神经网络CNN)(将在未来章节中讨论)来根据图像输入分类情感

虽然前述步骤资源消耗很大(因为我们需要很多人来收集和人工标记图像),但还有多种其他方式可以获得面部情感检测。我们不确定谷歌是如何收集和标记图像的,但我们现在将考虑谷歌为我们构建的 API,这样,如果我们想将图像分类为它们所代表的情感,我们可以利用该 API。

启用 API

在我们开始构建应用程序之前,我们首先必须启用 API,如下所示:

  1. 搜索 Google Cloud Vision API:

图片

  1. 启用 Google Cloud Vision API:

图片

  1. 一旦点击 ENABLE,API 将为项目(即我的第一个项目)启用,如前一张截图所示。

  2. 获取 API 凭据:

图片

  1. 点击创建凭据后点击服务帐户密钥:

图片

  1. 点击新建服务帐户:

图片

  1. 输入服务帐户名称(在我的情况下,kish-gcp)并选择项目所有者角色:

图片

  1. 点击创建以保存密钥的 JSON 文件。

打开实例

为了打开一个实例,点击 VM 实例,如下面的截图所示,然后点击激活 Google Cloud Shell 图标:

使用 Cloud Shell 创建实例

点击云壳图标后,我们创建了一个如下所示的实例:

  1. 通过指定以下代码创建一个实例:
datalab create --no-create-repository <instance name>
  1. 在 Cloud Shell 中,前面的代码如下所示:

  1. 一旦你为所有提示输入了响应,你需要将端口更改为8081以访问 Datalab,操作如下:

  1. 点击更改端口后,你会看到一个如下所示的窗口。输入8081并点击“更改并预览”以打开 Datalab:

  1. 这将打开 Datalab,它具有使我们能够编写所有类型命令的功能:bashbigquerypython等等。

现在需求已经设置好,让我们获取/安装 API 的需求:

  1. 在上一节中访问 API 密钥,我们已经下载了所需的密钥。现在,让我们通过点击上传按钮将.json文件上传到 Datalab:

  1. 一旦.json文件上传,你应该能够从这里通过 Datalab 访问它:

  1. 打开一个笔记本;你可以在 Datalab 中通过点击笔记本标签来打开一个笔记本,如下所示:

  1. 要安装google-cloud,一旦你打开笔记本,将内核从 python2 更改为 python3:

  1. 按照以下方式安装google-cloud包:
%bash
pip install google-cloud
  1. 一旦安装了google-cloud,确保之前上传的.json文件在当前 Python 环境中可访问,如下所示:
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-
api.json"
  1. 为了上传感兴趣的画面,我们将查看从本地机器到存储桶,以及从存储桶到 Datalab 的文件传输。

  2. 在 Google Cloud 中搜索bucket

  1. 现在,命名存储桶并创建它:

  1. 点击上传文件将相关文件从本地机器上传到存储桶。

  1. 一旦文件上传到存储桶,按照以下方式从 Datalab 获取它:

  1. 现在,你应该注意到11.jpg在 Datalab 中可访问。

现在要分析的画面在 Datalab 中可访问,让我们了解如何利用 Cloud Vision API 更好地理解图像:

  1. 导入相关包:
from google.cloud import vision

前面的代码片段确保了 Vision 中可用的方法在当前会话中可访问。

  1. 调用在客户端图像上执行 Google Cloud Vision API 检测任务(如人脸、地标、标志、标签和文本检测)的服务 - ImageAnnotator
client = vision.ImageAnnotatorClient()
  1. 确认图像已按预期上传:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
%matplotlib inline
img=mpimg.imread('/content/datalab/11.jpg')
plt.axis('off')
plt.imshow(img)

图片

  1. 调用face_detection方法以获取图像的相关详细信息,如下所示:
response = client.face_detection({'source' : {'image_uri': "gs://kish-
bucket/11.jpg"},})
  1. 图像注释的响应如下:

图片图片

  1. 现在我们已经运行了我们的方法来检测图像中的面部,让我们看看输出 - responseresponse的输出是一组属性,如前所述:
response

图片

以下是一些详细解释的额外点:

  • 边界多边形:边界多边形围绕人脸。边界框的坐标以原始图像的比例为单位,如ImageParams中返回的。边界框是根据人类预期计算出来的,以框定人脸。它基于人脸标记器的结果。注意,如果图像中只出现部分人脸,则BoundingPoly中可能不会生成一个或多个x和/或y坐标(多边形将是不封闭的)。

  • 人脸检测边界多边形fd_bounding_poly边界多边形比BoundingPoly更紧密,仅包围人脸的皮肤部分。通常,它用于从任何检测图像中可见皮肤数量的图像分析中消除人脸。

  • 地标:检测到的人脸关键点。

在以下要点中解释了几个更多术语:

  • roll_angle:翻滚角,表示人脸相对于图像的顺时针/逆时针旋转量。范围是[-180,180]。

  • pan_angle:偏航角,表示人脸相对于垂直于图像的平面的左/右角度。范围是[-180,180]。

  • tilt_angle:俯仰角,表示人脸相对于图像水平平面的向上/向下角度。范围是[-180,180]。

  • detection_confidence:与检测相关的置信度。

  • landmarking_confidence:与标记相关的置信度。

  • joy_likelihood:与快乐相关的似然性。

  • sorrow_likelihood:与悲伤相关的似然性。

  • anger_likelihood:与愤怒相关的似然性。

  • surprise_likelihood:与惊讶相关的似然性。

  • under_exposed_likelihood:与曝光相关的似然性。

  • blurred_likelihood:与模糊相关的似然性。

  • headwear_likelihood:与头部佩戴物相关的似然性。

人脸关键点将进一步提供眼睛、鼻子、嘴唇、耳朵等位置。

我们应该能够围绕识别出的人脸绘制一个边界框。

face_annotations的输出如下:

图片

从前面的代码中,我们应该能够理解边框的坐标。在接下来的代码中,我们计算边框的起始点,以及边框的相应宽度和高度。一旦计算完成,我们就在原始图像上叠加矩形:

import matplotlib.patches as patches
import numpy as np
fig,ax = plt.subplots(1)

# Display the image
ax.imshow(img)

# Create a Rectangle patch
x_width = np.abs(response.face_annotations[0].bounding_poly.vertices[1].x-
  response.face_annotations[0].bounding_poly.vertices[0].x)
y_height = np.abs(response.face_annotations[0].bounding_poly.vertices[1].y-
  response.face_annotations[0].bounding_poly.vertices[3].y)

rect =
 patches.Rectangle((response.face_annotations[0].bounding_poly.vertices[0].x,
 response.face_annotations[0].bounding_poly.vertices[0].y),
                         x_width,y_height,linewidth=5,edgecolor='y',facecolor='none')

# Add the patch to the Axes
ax.add_patch(rect)
plt.axis('off')
plt.show()

前述代码的输出是带有面部边框的图像,如下所示:

图片

标签检测

在前面的代码片段中,我们使用了face_detection方法来获取各种坐标。

为了理解图像的标签,我们将使用label_detection方法代替face_detection,如下所示:

response_label = client.label_detection({'source' : {'image_uri': "gs://kish-
bucket/11.jpg"},})

图片

标签检测的输出是一系列标签,以及与每个标签相关的分数。

文本检测

可以通过使用text_detection方法来识别图像中的文本,如下所示:

response_text = client.text_detection({'source' : {'image_uri': "gs://kish-
bucket/11.jpg"},})

response_text的输出如下:

图片

注意,text_detection方法的输出是图像中存在的各种文本的边框。

此外,请注意,text_annotations的描述提供了图像中检测到的文本。

标志检测

视觉服务还使我们能够通过使用logo_detection方法来识别图像中的标志。

在下面的代码中,你可以看到我们能够通过传递图像位置的 URL 来检测wikipedia的标志,如下所示:

response = client.logo_detection({'source' : {'image_uri':
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Wikipedia-logo-v2-
en.svg/135px-Wikipedia-logo-v2-en.svg.png"},})

logo_detection方法的输出如下:

图片

地标检测

注意,在前面的代码行中,我们在logo_detection方法中指定了图像位置的 URL,这导致了预测的标志描述,以及与其相关的置信度分数。

同样,任何位于图像中的地标都可以通过使用landmark_detection方法进行检测,如下所示:

response = client.landmark_detection({'source' : {'image_uri': 
 "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/
  Taj_Mahal_%28Edited%29.jpeg/250px-Taj_Mahal_%28Edited%29.jpeg"},})

landmark_detection方法的输出如下:

图片

云翻译 API

云翻译 API 提供了一个简单、程序化的接口,用于将任意字符串翻译成任何支持的语言,使用最先进的神经机器翻译。翻译 API 响应速度快,因此网站和应用程序可以集成翻译 API,以实现从源语言到目标语言的快速、动态翻译(例如,从法语翻译成英语)。对于源语言未知的情况,也提供了语言检测功能。

启用 API

为了我们能够使用 Google 云翻译服务,我们需要启用,这可以通过以下步骤完成:

  1. 为了启用 Google Cloud Translation API,在控制台中搜索该 API:

图片

  1. 启用 Google Cloud Translation API:

  1. 一旦启用翻译 API,下一步就是创建访问 API 的凭证。然而,请注意,如果您已经为某个 API 创建了凭证,则它们可以用于任何其他 API。让我们继续使用 Cloud Shell 初始化我们的实例:

  1. 一旦实例启动,我们将打开端口8081上的 Datalab。我们提供以下路径到api-key文件的位置:
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-api.json"
  1. 使用以下语句导入translate的各种方法:
from google.cloud import translate
  1. 创建一个client对象,用于连接到云翻译服务,如下所示:
client = translate.Client()

Google Cloud 翻译 API 有三个支持的方法,分别是get_languages()detect_language()translate()

  • client.get_languages()方法为我们提供了一个所有可用语言的列表,以及它们的缩写符号,如下所示:

  • client.detect_language()方法检测文本所使用的语言:

注意,在上述方法中,我们提供了两个文本——一个是西班牙语,另一个是英语。上述输出表示文本的语言,以及与语言检测相关的置信度。

  • client.translate()方法检测源语言并将文本翻译成英语(默认),如下所示:

  • client.translate()方法还提供了指定需要翻译成哪种目标语言的选项,如下所示:

自然语言 API

Google Cloud 自然语言 API 通过提供易于使用的 REST API 中的强大机器学习模型来揭示文本的结构和含义。您可以使用它从文本文档、新闻文章或博客文章中提取有关人物、地点、事件等信息,还可以用它来了解社交媒体上对您产品的看法,或从呼叫中心或消息应用中的客户对话中解析意图。您还可以分析请求中上传的文本,或将其与 Google Cloud 存储上的文档存储集成。

您可以通过在控制台中搜索它来找到云自然语言 API,如下所示:

云自然语言 API 已在生成的页面中启用:

与翻译 API 类似,如果至少已启用了一个 API,则无需为该 API 创建凭证。

自然语言处理在提取与各种文本相关的情感方面可能很有用。

情感分析检查给定的文本,并识别文本中的主导情感意见,以确定作者的立场是积极、消极还是中立。情感分析是通过 analyzeSentiment 方法进行的。

在以下示例中,让我们了解如何识别语句的情感:

  1. 导入相关包:
from google.cloud import language
  1. 初始化与语言服务对应的类:
client = language.LanguageServiceClient()

Google 自然语言 API 有以下支持的方法:

  • analyzeEntities

  • analyzeSentiment

  • analyzeEntitySentiment

  • annotateText

  • classifyText

每个方法都使用 Document 来表示文本。以下示例中,让我们探索 analyzeSentiment 方法:

text="this is a good text"
from google.cloud.language_v1 import types
document = types.Document(
        content=text,
        type='PLAIN_TEXT')
sentiment = client.analyze_sentiment(document).document_sentiment
sentiment.score

注意,我们已经将输入文本转换为 Document 类型,然后分析了文档的情感。

情感得分的输出反映了文本为正面的概率;得分越接近一,陈述就越积极。

类似地,可以传递一个 HTML 文件,如下所示:

存储在 Google Cloud 存储桶中的文件也可以通过将内容更改为 gcs_content_uri 来引用,如下所示:

analyze_entities() 方法在文本中找到命名实体(即专有名称)。此方法返回 AnalyzeEntitiesResponse

document = language.types.Document(content='Michelangelo Caravaggio, Italian    painter, is known for "The Calling of Saint Matthew".'
                                   ,type='PLAIN_TEXT') 
response = client.analyze_entities(document=document)

for entity in response.entities:
  print('name: {0}'.format(entity.name)) 

上述循环的输出是文档内容中存在的命名实体,如下所示:

我们还可以通过使用 analyze_syntax 方法提取给定文本中每个单词的词性,如下所示:

  1. 将文档标记化成构成文本的相应单词:
tokens = client.analyze_syntax(document).tokens
tokens[0].text.content
# The preceding output is u'Michelangelo'
  1. 然后,可以提取 token 的词性,如下所示:
pos_tag = ('UNKNOWN', 'ADJ', 'ADP', 'ADV', 'CONJ', 'DET', 'NOUN', 'NUM','PRON', 'PRT', 'PUNCT', 'VERB', 'X', 'AFFIX')
for token in tokens:print(u'{}: {}'.format(pos_tag[token.part_of_speech.tag],
                               token.text.content))

上述代码的输出如下:

注意,大多数单词都被正确地分类为词性。

语音转文本 API

Google Cloud Speech API 允许开发者通过应用易于使用的 API 中的强大神经网络模型将音频转换为文本。该 API 识别超过 110 种语言和变体。可以将用户对应用程序麦克风的语音指令转录成文本,通过语音实现命令和控制,或者转录音频文件,等等。

为了启用语音转文本 API,在控制台中搜索它,如下所示:

在生成的网页中,启用 API,如下所示:

与前几节中提到的 API 类似,为其中一个 API 获得的凭证可以复制用于其他 Google API。因此,我们不需要为语音转文本 API 分别创建凭证。

一旦启用 API,让我们启动 Cloud Shell 和 Datalab,就像前几节中做的那样。

在以下代码中,我们将一个小音频文件转录成文本:

  1. 导入相关包和 API 密钥:
from google.cloud import speech
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-api.json"
from google.cloud.speech import enums
from google.cloud.speech import types
  1. 按照以下步骤调用语音服务:
client = speech.SpeechClient()
  1. 按以下方式指定我们想要转换的音频:
audio = types.RecognitionAudio(uri='gs://kish-bucket/how_are_you.flac')

注意无损音频编解码器FLAC)。

可以使用位于audio.online-convert.com/convert-to-flac的转换器将音频文件(.wav)转换为.flac文件。

文件位于我们之前创建的存储桶中。我们指定音频配置,如下所示:

config = types.RecognitionConfig(
encoding=enums.RecognitionConfig.AudioEncoding.FLAC,
sample_rate_hertz=16000,
language_code='en-US')

通过传递audio内容和指定的配置来获得响应:

response = client.recognize(config, audio)

现在可以按照以下方式访问结果:

for result in response.results: 
  print(result)

这的输出如下:

图片

当输入音频文件是短(<1 分钟)持续时间音频时,recognize方法才会工作。

如果音频文件持续时间更长,则应使用long_running_recognize方法:

operation = client.long_running_recognize(config, audio)

可以通过指定以下内容来访问result

response = operation.result(timeout=90)

最后,可以通过打印响应结果来获取转录和置信度,就像之前做的那样。

视频智能 API

云视频智能 API 通过使用易于使用的 REST API 提取元数据,使视频可搜索和可发现。现在,您可以搜索目录中每个视频文件的每一刻。它快速标注存储在 Google Cloud 存储中的视频,并帮助您识别视频中的关键实体(名词)及其出现时间。

可以按照以下方式搜索和启用云视频智能 API:

图片图片

按以下方式导入所需的包并添加api-key的路径:

from google.cloud import videointelligence
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-api.json"
from google.cloud.speech import enums
from google.cloud.speech import types

features方法使我们能够指定我们想要在视频中检测的内容类型。可用的功能如下:

图片

让我们继续检测我们感兴趣的视频中标签:

features = [videointelligence.enums.Feature.LABEL_DETECTION]

按以下方式指定视频的config和上下文:

mode = videointelligence.enums.LabelDetectionMode.SHOT_AND_FRAME_MODE
config = videointelligence.types.LabelDetectionConfig(
    label_detection_mode=mode)
context = videointelligence.types.VideoContext(
    label_detection_config=config)

然后,需要按照以下方式将视频从云存储传递过来:

path="gs://kish-bucket/Hemanvi_video.mp4"
operation = video_client.annotate_video(
        path, features=features, video_context=context)

按以下方式访问annotate_video方法的输出:

result = operation.result(timeout=90)

可以在以下级别获得视频的注释结果:

  • 视频段级别

  • 视频镜头级别

  • 帧级别

在遍历每个不同段标签注释后,可以在以下方式获得段级别结果:

segment_labels = result.annotation_results[0].segment_label_annotations
for i, segment_label in enumerate(segment_labels):
    print('Video label description: {}'.format(
        segment_label.entity.description))
    for category_entity in segment_label.category_entities:
        print('\tLabel category description: {}'.format(
            category_entity.description))

    for i, segment in enumerate(segment_label.segments):
        start_time = (segment.segment.start_time_offset.seconds +
                      segment.segment.start_time_offset.nanos / 1e9)
        end_time = (segment.segment.end_time_offset.seconds +
                    segment.segment.end_time_offset.nanos / 1e9)
        positions = '{}s to {}s'.format(start_time, end_time)
        confidence = segment.confidence
        print('\tSegment {}: {}'.format(i, positions))
        print('\tConfidence: {}'.format(confidence))
    print('\n')

上述代码的输出如下:

图片

同样,可以按照以下方式获得镜头级别的结果:

shot_labels = result.annotation_results[0].shot_label_annotations
for i, shot_label in enumerate(shot_labels):
    print('Shot label description: {}'.format(
        shot_label.entity.description))
    for category_entity in shot_label.category_entities:
        print('\tLabel category description: {}'.format(
            category_entity.description))

    for i, shot in enumerate(shot_label.segments):
        start_time = (shot.segment.start_time_offset.seconds +
                      shot.segment.start_time_offset.nanos / 1e9)
        end_time = (shot.segment.end_time_offset.seconds +
                    shot.segment.end_time_offset.nanos / 1e9)
        positions = '{}s to {}s'.format(start_time, end_time)
        confidence = shot.confidence
        print('\tSegment {}: {}'.format(i, positions))
        print('\tConfidence: {}'.format(confidence))
    print('\n')

上述代码行的输出如下:

图片

最后,可以按照以下方式获得帧级别的结果:

frame_labels = result.annotation_results[0].frame_label_annotations
for i, frame_label in enumerate(frame_labels):
    print('Frame label description: {}'.format(
        frame_label.entity.description))
    for category_entity in frame_label.category_entities:
        print('\tLabel category description: {}'.format(
            category_entity.description))

    # Each frame_label_annotation has many frames,
    # here we print information only about the first frame.
    frame = frame_label.frames[0]
    time_offset = (frame.time_offset.seconds +
                   frame.time_offset.nanos / 1e9)
    print('\tFirst frame time offset: {}s'.format(time_offset))
    print('\tFirst frame confidence: {}'.format(frame.confidence))
    print('\n')

上述代码行的输出如下:

图片

摘要

在本章中,我们了解了谷歌提供的主要机器学习 API:视觉、翻译、自然语言处理、语音和视频智能。我们学习了每个 API 中的各种方法如何使我们能够复制深度学习结果,而无需从头编写代码。

第八章:使用 Firebase 创建机器学习应用程序

在上一章中,我们学习了如何使用一些 Google 机器学习 API 来预测/分类事件。然而,我们在 Datalab 中完成了所有工作。在实际场景中,我们可能希望将机器学习 API 集成到我们构建的网络应用程序或移动应用程序中。在这种情况下,Firebase 非常有用。Firebase 是一个平台,允许我们构建无需服务器端编程的 Web 和移动应用程序。Firebase 提供了多个功能,确保开发者专注于构建应用程序,而后端由 Firebase 负责。Firebase 提供的一些功能包括:

  • 实时数据库

  • 文件存储

  • 云函数

  • 主机服务

  • 性能监控

  • 分析

  • 身份验证

在本章中,我们将了解 Firebase 提供的各种功能。此外,为了了解 Firebase 如何帮助构建具有机器学习功能的应用程序,我们将构建一个网络应用程序和一个移动应用程序,该应用程序使用 Google 翻译 API 将任何给定语言的文本翻译成英语,并提供最常翻译的文本。

Firebase 的功能

Firebase 提供的一些功能如下:

  • 实时数据库:使我们能够在毫秒内存储和同步应用程序数据

  • 云 Firestore:使我们能够在全球范围内存储和同步数据

  • 云函数:使我们能够在不管理服务器的情况下运行后端代码

  • 主机服务:以速度和安全交付网络应用程序资产

  • 性能监控:帮助我们深入了解应用程序的性能

  • 崩溃分析:使我们能够通过强大的实时崩溃报告来优先处理和修复问题

  • 身份验证:帮助我们简单且安全地验证用户

  • 云存储:使我们能够在 Google 规模上存储和提供文件

  • 预测:使我们能够根据预测的行为定义动态用户组

  • 远程配置:使我们能够在不部署新版本的情况下修改我们的应用程序

  • 应用索引:使我们能够将搜索流量引导到移动应用程序

  • 云消息服务:使我们能够发送有针对性的消息和通知

  • 动态链接:使我们能够通过使用带有归因的深度链接来驱动增长

  • 邀请:使我们能够通过使用带有归因的深度链接来驱动增长

在本章中,我们将构建一个应用程序,该应用程序可以将输入文本翻译成英语——首先是一个网络应用程序,然后是一个移动应用程序。

构建网络应用程序

为了构建网络应用程序,我们使用了 Node.js。

下载并安装 Node.js:

  1. node.js可以从以下链接下载:nodejs.org/en/

    对于我们现在正在构建的版本,我们将在 Windows 64 位机器上使用 Node.js 的 8.11.1 LTS 版本。

  2. 下载可执行文件 nodejs.org/dist/v8.11.1/node-v8.11.1-x64.msi 后,请确保使用默认参数安装 Node.js。

创建 Firebase 项目:

  1. 可以通过登录 Firebase 控制台在这里创建 Firebase 项目:console.firebase.google.com

  2. 在控制台中,点击添加项目:

  1. 输入项目名称(用红色突出显示)并获取项目 ID(用黑色突出显示),如下所示:

  1. 使用 Node.js 包管理器安装 Firebase 工具,如下所示:

    • 将目录更改为需要存储 firebase 函数文件的文件夹。在以下屏幕截图中,我们在 E 驱动器中创建了一个名为 firebase 的文件夹:

  1. 我们使用以下代码片段安装 Firebase 工具:

登录并初始化 Firebase:

  1. 通过指定以下内容登录 Firebase:
firebase login --reauth
  1. 之前的代码片段将允许我们使用我们的凭据登录。请确保允许 Firebase CLI 访问您的 Google 账户。

  2. 一旦我们登录到 Firebase,我们就可以如下初始化 firebase

firebase init
  1. 你将看到以下屏幕:

  1. Y 初始化 Firebase。

  2. 通过按空格键选择当前应用程序所需的特性,选择完成后按 Enter 键:

  1. 选中后,对于我们这里使用的版本,让我们指定我们的函数使用 JavaScript 部署,如下所示:

  1. 选中后,我们使用项目目录设置项目:

注意 mytranslator 是我们在 步骤 2 中创建的项目。还请注意,一旦我们初始化了 Firebase,文件夹结构如下所示:

  1. 在命令提示符中,初始化 Firebase 后按 Enter 键以获取各种提示。初始化完成后,你应该会收到以下确认信息:

在滚动到 functions 文件夹后,使用 Node.js 包管理器安装 Google Translate:

我们指定了我们用例所需的所有功能(公共 API 方法)。这些函数处理所有服务器编程:

  1. 为了指定这些,让我们用以下代码片段覆盖 functions 文件夹中现有的 index.js 文件。
const functions = require('firebase-functions');
const Translate = require('@google-cloud/translate');
const admin = require("firebase-admin")

//setting connection to db
admin.initializeApp();

const translate = new Translate({
    projectId: 'mytranslator-c656d'
});
//Extract the most searched term

exports.getMessageStats=functions.https.onRequest((req,res) =>
 {
 var output;
 var db = admin.database();
 var ref = db.ref("/translateMessageStats");

// Attach an asynchronous callback to read the data at our posts reference
 ref.orderByChild("count").limitToLast(1).on("value", function(snapshot) {

console.log(snapshot.forEach(element => {
 output=element.key+" : "+element.val().count + 'times'
 }))
 res.header("Access-Control-Allow-Origin", "*");
 return res.send(JSON.stringify(output));
 }, function (errorObject) {
 console.log("The read failed: " + errorObject.code);
 });
})

// create a public API method of name "translateMessage"

exports.translateMessage=functions.https.onRequest((req,res) =>
 {
 const input = req.query.text;

translate.translate(input,'en').then(results =>
 {
 const output = results[0];
 console.log(output);

const db = admin.database();
var ref = db.ref("/translateMessageStats");

//update database
 var dataRef= ref.child(input);

dataRef.once('value', function(snapshot) {
 if (snapshot.exists()) {
 dataRef.update({"count":snapshot.val().count+1});
 console.log("data exists")
 }
 else
 {
 console.log("data does not exist")
 dataRef.update({"count":1});
 }
 });

res.header("Access-Control-Allow-Origin", "*");
 return res.send(JSON.stringify(output));
 })
});
  1. 在此代码中,我们通过以下代码导入所需的 Node.js 包:
const functions = require('firebase-functions');
const Translate = require('@google-cloud/translate');
const admin = require("firebase-admin")
  1. 我们通过指定以下内容初始化与数据库的连接:
admin.initializeApp();
  1. 我们创建translate对象,并将项目 ID 作为参数传递给它,如下所示:
const translate = new Translate({
    projectId: 'mytranslator-c656d'
});
  1. 然后,我们创建一个名为translateMessage的公开 API,如下所示:
exports.translateMessage=functions.https.onRequest((req,res) =>
  1. 用户输入通过以下行获取:
const input = req.query.text;
  1. 输入文本的翻译和相应存储在输出中的翻译文本是通过此代码完成的:
translate.translate(input,'en').then(results =>
{
    const output = results[0];
  1. 我们创建一个数据库实例,如下所示:
const db = admin.database();
 var ref = db.ref("/translateMessageStats");
  1. 输入在数据库中更新:
var dataRef= ref.child(input);
  1. 如果给出新的输入,则count初始化为1;否则,count增加1
dataRef.once('value', function(snapshot) {
    if (snapshot.exists()) {
        dataRef.update({"count":snapshot.val().count+1});
        console.log("data exists")
    }
    else
    {
        console.log("data does not exist")
        dataRef.update({"count":1});
    }
  });

启用Google Cloud Translation API,如下所示:

部署firebase函数:

  1. 我们可以按照以下截图所示部署firebase函数:

  1. 函数部署后,检查项目概述的开发部分中的函数:

  1. 点击函数后,我们应该能够看到一个包含我们刚刚创建的函数translateMessage的仪表板:

注意,前面的事件为我们提供了一个 URL,使用该 URL 我们应该能够翻译输入文本,如下所示:

注意 URL 中?text=的使用,这是输入。

如果执行过程中出现问题,我们应该能够在函数仪表板的日志标签中理解它们。

此外,我们搜索过的所有输入都存储在数据库中:

注意,计数值初始化为搜索术语hola

public文件夹中的index.html文件的内容替换为以下代码片段。以下代码片段的输出将创建一个文本框,允许我们输入文本,翻译文本,并生成翻译后的输出。

  1. 在您的执行中,将项目 ID mytranslator-c656d替换为您自己的项目 ID:
<html>
  <script src="img/jquery.min.js">  </script>
  <script>

    $(document).ready(
      getMessageStats()
    );
<!-- The following code extracts the most searched term from the database that we create in the next function -->

    function getMessageStats(){
      var xhr = new XMLHttpRequest();
      xhr.open('GET', "https://us-central1-mytranslator-c656d.cloudfunctions.net/getMessageStats", true);
      xhr.send();

      xhr.onreadystatechange = processRequest;

      function processRequest(e) {
        if (xhr.readyState == 4) {
          var response = JSON.parse(xhr.responseText);
          document.getElementById("mostSearched").innerHTML=response;
        }
      }
    }
<!-- the following function translates the input value into english -->
    function translateText()
    {
      var textInput= document.getElementById("input").value;
      var xhr = new XMLHttpRequest();
      xhr.open('GET', "https://us-central1-mytranslator-c656d.cloudfunctions.net/translateMessage?text="+textInput, true);
      xhr.send();

      xhr.onreadystatechange = processRequest;
      function processRequest(e) {

        if (xhr.readyState == 4) {
          var response = JSON.parse(xhr.responseText);
          document.getElementById("output").innerHTML=response;
          getMessageStats();
        }
      }
    }
  </script>
<!-- the following code creates the layout of web application, with input text box and output-->
  <body>
    <label>Enter Input Text</label>
    <input id="input" type="text-area"></input>
    <button onclick="translateText()" id="btnTrans">Translate</button>
    <label id="output"></label>
    <br/>
    <div>
      <h1>Most Searched element</h1>
      <label id="mostSearched"></label>
    </div>
  </body>
</html>

我们部署 Firebase,以便上传指定最终 URL 结构的 HTML 文件:

现在,我们应该能够访问显示的链接,这有助于我们翻译文本,如下所示:

从这个例子中,我们看到我们能够创建一个翻译任何给定输入文本的 Web 应用程序。请注意,Web 应用程序使用了函数创建的 API 端点,而前端代码将根据我们使用的框架而有所不同——当我们使用 Angular 而不是 HTML 时,它可能会有所不同,但服务器端代码将保持不变。

构建移动应用程序

在上一节中,我们了解了将为我们翻译输入的 HTML 页面的前端。在本节中,我们将构建利用我们为函数生成的端点返回翻译文本的 Android 应用程序的前端。

我们创建应用的布局如下:

<?xml version="1.0" encoding="utf-8"?>
 <android.support.constraint.ConstraintLayout 

     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">

     <Button
         android:id="@+id/button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="148dp"
         android:layout_marginTop="56dp"
         android:text="Translate"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/input" />

     <EditText
         android:id="@+id/input"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="84dp"
         android:layout_marginTop="84dp"
         android:ems="10"
         android:inputType="textPersonName"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />

     <TextView
         android:id="@+id/out"
         android:layout_width="197dp"
         android:layout_height="80dp"
         android:layout_marginStart="92dp"
         android:layout_marginTop="56dp"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/button" />

 </android.support.constraint.ConstraintLayout>

前面的代码会输出应用的布局:

注意,我们有一个用于接收输入的 EditText 视图。

按钮用于执行翻译,而 out 是用于显示输出的 TextView。

此外,请注意,在前面的代码中,我们确保了组件对齐到屏幕。

在主活动(main activity)中,我们执行以下代码:

package com.example.admin.translateapp;

 import android.os.AsyncTask;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;

 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;

 import javax.net.ssl.HttpsURLConnection;

 public class MainActivity extends AppCompatActivity {
     public String myurl;
     public String result;
     public String response;
     public EditText inp;
     public TextView out;
     public Button btn;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         inp = (EditText) findViewById(R.id.input);
         out = (TextView) findViewById(R.id.out);
         btn = (Button) findViewById(R.id.button);
         myurl = "http://us-central1-mytranslator-c656d.cloudfunctions.net/translateMessage?text=";

         btn.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 RequestTask task = new RequestTask();
                 task.execute(inp.getText().toString());
             }
         });
     }

     private class RequestTask extends AsyncTask<String, String, String> {

         @Override
         protected String doInBackground(String... uri) {
             try {
                 URL url = new URL(myurl+uri[0].toString());
                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                 conn.setRequestMethod("GET");
                 conn.connect();
                 if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                     InputStreamReader streamReader = new
                             InputStreamReader(conn.getInputStream());
                     //Create a new buffered reader and String Builder
                     BufferedReader reader = new BufferedReader(streamReader);
                     StringBuilder stringBuilder = new StringBuilder();
                     //Check if the line we are reading is not null
                     String inputLine;
                     while((inputLine = reader.readLine()) != null){
                         stringBuilder.append(inputLine);
                     }
                     //Close our InputStream and Buffered reader
                     reader.close();
                     streamReader.close();
                     //Set our result equal to our stringBuilder
                     result = stringBuilder.toString();
                     //result = conn.getResponseMessage();
                 } else {
                 }
             } catch (IOException e) {
                 //TODO Handle problems..
             }
             return result;
         }

         @Override
         protected void onPostExecute(String result1) {
             super.onPostExecute(result1);
             out.setText(result1); }
     }
 }

让我们理解前面的代码。

导入相关包:

import android.os.AsyncTask;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;

 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;

初始化我们在MainActivity类中使用的对象:

public String myurl;
 public String result;
 public String response;
 public EditText inp;
 public TextView out;
 public Button btn;

此外,使用以下代码初始化视图:

inp = (EditText) findViewById(R.id.*input*);
 out = (TextView) findViewById(R.id.*out*);
 btn = (Button) findViewById(R.id.*button*);

设置点击监听器:

btn.setOnClickListener(new View.OnClickListener() {
     public void onClick(View v) {
         RequestTask task = new RequestTask();
         task.execute(inp.getText().toString());

指定点击按钮时需要执行的任务:

URL url = new URL(myurl+uri[0].toString());
 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 conn.setRequestMethod("GET");
 conn.connect();
 if (conn.getResponseCode() == HttpURLConnection.*HTTP_OK*) {
     InputStreamReader streamReader = new
             InputStreamReader(conn.getInputStream());
     //Create a new buffered reader and String Builder
     BufferedReader reader = new BufferedReader(streamReader);
     StringBuilder stringBuilder = new StringBuilder();
     //Check if the line we are reading is not null
     String inputLine;
     while((inputLine = reader.readLine()) != null){
         stringBuilder.append(inputLine);
     }
     //Close our InputStream and Buffered reader
     reader.close();
     streamReader.close();
     //Set our result equal to our stringBuilder
     result = stringBuilder.toString();

从前面的代码中,URL 被评估为我们在之前网络应用部分看到的 URL。

前面代码的输出如下:

注意,点击按钮时,我们应该能够翻译我们的文本。

概述

在本章中,我们了解了 Firebase 的各种功能,并使用firebase函数构建网络和移动应用的底层。我们还使用firebase函数实时更新数据库,并从数据库中检索历史上搜索最多的术语。

第九章:使用 TensorFlow 和 Keras 的神经网络

神经网络是一种受大脑功能启发的不严格监督学习算法。类似于大脑中神经元相互连接的方式,神经网络接收输入并通过一个函数传递它,基于此,某些后续神经元被激活,从而产生输出。

在本章中,我们将关注使用 TensorFlow 和 Keras 的神经网络的实际实现。TensorFlow 提供了一个低级框架来创建神经网络模型。Keras 是一个高级神经网络 API,它显著简化了定义神经网络模型的任务。我们将展示如何在 TensorFlow 上使用 Keras 来定义和训练 GCP 上的模型。我们将以 Python 中的 Keras API 展示,并使用经典 MNIST 数据集上的简单前馈网络进行操作。此外,我们还将介绍神经网络的各个组成部分:

  • 初始化

  • 指标和损失函数

  • 激活函数

  • 网络深度

神经网络概述

神经网络的起源在于每个函数都不能用线性/逻辑回归来近似——数据中可能存在只能由复杂函数近似的潜在复杂形状。

函数越复杂(有某种处理过拟合的方法),预测精度就越好。

以下图像解释了神经网络如何将数据拟合到模型中的方式。

神经网络的典型结构如下:

在此图中,输入层通常由用于预测输出(因变量)层或层的独立变量组成。

隐藏层用于将输入变量转换成高阶函数。隐藏层转换输出的方式如下:

在前面的图中,x[1]x[2]、...、x[n] 是独立变量,而 x[0] 是偏置项(类似于我们在线性/逻辑回归中有一个偏置的方式)。

w[1]w[2]、...、w[n] 是分配给每个输入变量的权重。如果 a 是隐藏层中的一个神经元,它将等于:

在此方程中我们看到的是我们在求和之上应用的激活函数,以便我们获得非线性。我们需要非线性,以便我们的模型能够学习复杂的模式。

此外,拥有多个隐藏层有助于实现高非线性。

下面的章节将提供可以调整的神经网络各种参数的详细信息。

设置 Google Cloud Datalab

为了设置 Google Cloud Datalab,我们点击 Cloud Shell 图标:

在 Cloud Shell 中,设置需要工作的项目,如下所示:

gcloud config set core/project gcp-test-196204

一旦设置了项目,按照以下方式配置区域:

gcloud config set compute/zone us-west1-b

最后,通过指定以下内容创建一个 Datalab 实例:

  • 对于 CPU 版本:
datalab create --no-create-repository mlgcp
  • 对于 GPU 版本:

首先,您需要通过配额页面请求 GPU 版本,如下所示:

图片

提交配额请求,您应该很快就会收到在指定区域使用 GPU 的权限。

注意,在构建神经网络模型时,GPU 版本更好,因为 GPU 中的多个处理器可以并行更新神经网络中的多个权重。

将端口更改为 8081 以打开 Datalab 并因此打开笔记本。

安装和导入所需的包

TensorFlow 作为包,是为了执行神经网络计算而构建的。它使用懒加载评估概念,即在执行代码之前,需要指定神经网络连接的各种元素。

另一个名为 Keras 的 API 使得构建神经网络变得容易得多。在本章中,我们将首先利用 TensorFlow 后端运行的 Keras 包,然后我们将展示如何使用 TensorFlow 中的预制估计器和自定义估计器构建神经网络。

在前面的章节中,我们了解了如何设置 Datalab 笔记本。在本章中,我们将看到如何将所需的包安装并导入到 Datalab 笔记本中。

默认情况下,Datalab 预装了 TensorFlow 包。然而,它默认不包含 Keras。让我们看看如何安装 keras 包:

!pip install keras

一旦安装了 Keras,让我们导入所需的两个包:

import keras as K
import tensorflow as tf

简单神经网络的详细工作原理

为了理解神经网络是如何工作的,我们将构建一个非常简单的网络。输入和预期的输出如下:

import numpy as np
x=np.array([[1,2],[3,4]])
y=np.array([0,1])

注意,x 是包含两个变量的输入数据集,每个行有两个变量。y 是两个输入的预期输出。

实质上,我们已经建立了输入和输出层。

例如,对于前面数据点中的一个,网络的输入和输出值将如下所示:

图片

在传统的机器学习中,您会直接在输入和输出值之间找到关系。然而,神经网络架构的工作原理如下:

"输入值可以表示在一个更丰富(更高)的维度空间中。输入值所表示的维度越多,输入数据集中的复杂性就越大。"

基于前面的直觉,让我们在神经网络中构建一个包含三个单元的隐藏层:

图片

现在层已经构建,让我们按照以下方式在每个单元之间建立连接:

图片

现在已经建立了每个单元之间的连接,每个连接将会有一个与之相关的权重。在下面的图中,我们将初始化每个连接所代表的权重:

注意,权重W代表连接的强度。

现在我们已经构建了一个简单的神经网络。让我们随机初始化输入层和隐藏层之间的权重值,以了解隐藏层值是如何计算的:

隐藏层值是输入值和与之相关的权重的乘积,如下所示:

h1 = 11 + 2*(2) = 5*

h2 = 10.5 + 2*(-1) = -1.5*

h3 = 1(-0.2) + 20.1 = 0

现在隐藏值已经计算出来,我们将它们通过一个激活函数。激活函数的直觉如下:

"我们之前呈现的神经网络状态(没有激活函数)是输入变量的一个大线性组合。非线性只能通过对隐藏层值进行激活来获得。"

为了简化,目前我们将假设我们要应用的非线性是 sigmoid 函数。

Sigmoid 函数的工作原理如下:

  • 它接受一个输入值x,并将其转换成新的值1/(1+exp(-x))

Sigmoid 曲线的非线性对于各种x的值看起来是这样的:

因此,隐藏层值,即 5,-1.5 和 0,被转换成0.990.180.5

现在隐藏层值已经计算出来,让我们初始化连接隐藏层到输出层的权重。

注意,权重再次是随机初始化的:

现在权重已经初始化,让我们计算与输出层相关的值:

0.991 + 0.18*(-1) + 0.50.2 = 0.91

输出层的预期值是0.91,而实际值是 0。

因此,在这种情况下相关的损失是*(0.91 - 0)² = 0.83*。

到目前为止的过程,即计算与权重值相对应的损失,被称为前向过程

到目前为止,在本节中,我们已经理解了:

  • 权重

  • 激活函数

  • 损失计算

在先前的场景中,虽然对于我们要尝试解决的给定目标,损失函数保持不变,但权重初始化和激活函数可以针对不同的网络架构而变化。

对于刚才提出的这个问题,目标是通过迭代地改变权重来最小化与网络架构相对应的损失。

例如,在先前的架构中,可以通过将隐藏层到输出层连接的最终权重从0.2更改为0.1来减少损失。一旦权重改变,损失从0.83减少到0.74

通过迭代改变权重以最小化损失值的过程称为反向传播

每个给定数据集中权重变化发生的次数称为epoch。本质上,一个 epoch 由前向传播和反向传播组成。

智能地达到最佳权重值的一种技术称为梯度下降——关于各种权重优化器将在后面的章节中详细介绍。

反向传播

在上一节中,我们看到了反向传播中权重如何更新的直觉。在本节中,我们将看到权重更新过程的详细细节:

在反向传播过程中,我们从神经网络末端的权重开始,向后工作。

在先前的图(1)中,我们通过迭代地以小量(0.01)改变每个连接隐藏层到输出层的权重值:

原始权重变化后的权重误差误差减少量
11.010.84261-1.811
-1-0.990.849-0.32
0.20.210.837-0.91

从前面的表中,我们注意到,为了提高误差,应该减少权重值,而不是增加它们:

原始权重变化后的权重误差误差减少量
10.990.81081.792
-1-1.010.82480.327
0.20.190.8190.9075

现在我们注意到,对于某些权重更新,误差的改进很高,而对于其他一些权重更新,误差的改进较低。

这表明,对于某些误差改进很大的权重,权重更新可以更快;而对于误差改进相对较低的权重,权重更新可以更慢。

对于值为 1 的权重,其变化后的权重可能是:

变化后的权重 = 原始权重 + 学习率 X 误差减少量

现在,让我们假设学习率为0.05;那么:

变化后的权重 = 1 + 0.05(1.792) = 1.089*

其他权重将使用相同的公式进行更改。

直觉上,学习率帮助我们建立对算法的信任。例如,在决定权重更新的幅度时,我们可能会选择不是一次性改变所有内容,而是采取更谨慎的方法,更慢地更新权重。

一旦使用所描述的过程更新了所有权重,反向传播过程就完成了,然后我们再次进行前向传播。

前向传播和反向传播步骤合在一起称为一个 epoch

注意,我们一次计算预测一个数据点的误差值,从而形成一个大小为 1 的批次。在实践中,我们计算一组数据点的误差值,然后使用一个数据批次而不是单个数据点来更新权重。

在 Keras 中实现简单的神经网络

从前面的讨论中,我们已经看到神经网络中的关键组件是:

  • 隐藏层

  • 隐藏层的激活

  • 损失函数

除了这些,神经网络中还有一些其他关键组件。然而,我们将在后面的章节中学习它们。

现在,我们将使用我们到目前为止所获得的知识,在 Keras 中构建一个具有给定玩具数据集的神经网络模型:

导入相关函数:

图片

序列模型是层(输入、隐藏和输出)的线性堆叠。

在每一层中,dense 帮助实现网络中指定的操作。

让我们继续构建网络,如下所示:

图片

在我们的数据中,我们首先将二维的输入数据集转换为三维的隐藏层单元。

一旦计算了隐藏层的值,我们将在第二步通过 sigmoid 激活函数传递它们。

前两个步骤在模型指定的第二行中得到了体现。

从隐藏层,我们将其连接到一个一维的输出层,因此第三行代码有 Dense(1)

让我们看看我们指定的模型摘要:

图片

让我们了解前面总结中的输出形状列:(None, 3)

None 表示输出与输入数量无关(不要与输入维度混淆)。3 表示隐藏层中的单元数量。

同样地,第二层中的 (None,1) 代表输出层的维度(输出层只有一个单元)。

Param # 表示与网络相关的参数数量。

注意,输入层和隐藏层之间的连接总共有九个参数,因为有六个权重值(如前一小节中的图所示),以及与隐藏层中每个单元相关的三个偏置项。

同样地,隐藏层和输出层之间有四个参数,因为隐藏层和输出层之间有三个权重值,还有一个与输出层相关的偏置项。

现在既然已经指定了网络架构,让我们编译模型,如下所示:

图片

在上一行代码中,我们指定损失是基于均方误差计算的,这是实际值与预测值之间平方差的平均值,涉及输入数据集中的所有数据点。

类似地,我们指定优化技术基于随机梯度下降。

现在我们已经指定了模型结构、我们正在计算的损失函数以及我们正在使用的优化技术,让我们将模型拟合到输入和输出值。

在拟合模型时需要指定的附加指标是:

  • 输入和输出值

  • 在模型上运行的 epoch 数量:

图片

注意,我们指定的输入和输出变量是xy

还应该注意到,随着权重值调整以尽可能在 10 个 epoch 内最小化损失,损失值在不同的 epoch 中会降低。

现在我们已经建立了模型,让我们看看如何获得每一层的权重值:

图片

现在可以按照以下方式计算对应于新输入值的值:

图片

在前面的代码片段中,我们初始化了一个新的输入,并使用通过运行模型获得的最佳权重预测了对应于这个新输入的输出。

让我们了解如何获得输出。

获得隐藏层中三个单元的值:

h1 = 2(-0.985) + 5*(-0.3587) + 0.00195 = -3.76*

h2 = 20.537 + 5*(-0.8225) + 0.0011 = -3.025*

h3 = 2(-0.24) + 50.98 - 0.0027 = 4.421

一旦计算了隐藏层值,我们就将它们通过模型架构中指定的 sigmoid 激活函数传递。

final h1 = sigmoid(h1) = 0.0226

final h2 = sigmoid(h2) = 0.0462

final h3 = sigmoid(h3) = 0.988

一旦获得了最终的隐藏层单元值,我们将它们与连接隐藏层到输出层的权重相乘,如下所示:

Output = 0.0226 * 0.834 + 0.0462 * 0.6618 + (-0.401) * 0.988 + 0.14615 = -0.20051

注意,我们获得的是与model.predict函数中获得相同的值。这证明了我们迄今为止所学的架构功能。

现在我们已经建立了模型,让我们重新执行我们的代码,看看结果是否保持不变:

图片

注意,损失值与我们之前迭代中获得的不同。这是因为神经网络运行的第一个 epoch 中权重是随机初始化的。一种修复方法是设置一个种子。种子有助于在每次神经网络运行时初始化相同的随机值集合。

注意,每次重建模型时都应该运行种子。设置种子的代码片段如下:

图片

理解各种损失函数

如前一章所述,存在两种类型的因变量——连续变量和分类变量。在连续变量预测的情况下,损失(误差)函数可以通过计算所有预测的平方误差值之和来计算。

在因变量是与其相关联的唯一两个不同值的分类变量的情况下,损失是通过使用此公式的二进制交叉熵误差来计算的:

ylogp + (1-y)log(1-p)

在因变量是具有多个不同值的分类变量的情况下,损失是通过使用分类交叉熵误差来计算的:

∑ ylogp*

其中 p 是事件为 1 的概率。

在实践中,分类变量通常按以下方式进行 one-hot 编码:

假设三个不同行的输出为[1,2,3];则输出值表示为[[1,0,0], [0,1,0], [0,0,1]]。其中每个索引值表示是否存在一个不同的值。在上面的例子中,零索引对应于 1,因此只有第一行在零索引处有值为 1,其余的值为 0。

Keras 中可用的其他损失函数包括:

  • 均方绝对误差

  • 均方绝对百分比误差

  • 均方对数误差

  • 平方铰链

  • 铰链

  • 分类铰链

  • Logcosh

Softmax 激活

从前面的章节中,我们应该注意到,在分类变量预测的情况下,输出层的单元数将与因变量中不同值的数量相同。

此外,请注意,输出层的任何单元的预测值都不能大于 1 或小于 0。同时,输出层中所有节点值的总和应等于 1。

例如,假设两个输出节点的输出值分别为-1 和 5。鉴于输出值的期望值应在 0 到 1 之间(即事件发生的概率),我们将输出值通过 softmax 激活函数传递,如下所示:

  • 通过指数函数传递值:

exp(-1) = 0.367

exp(5) = 148

  • 将输出值归一化以获得介于 0 到 1 之间的概率,并确保两个输出节点之间的概率总和为 1:

0.367/(0.367+148) =0.001

148/(0.367+148) = 0.999

因此,softmax 激活帮助我们将输出值转换为概率数。

在 Keras 中构建更复杂的网络

到目前为止,我们已经构建了一个相当简单的神经网络。传统的神经网络会有更多可变的参数,以实现更好的预测能力。

让我们通过使用经典的 MNIST 数据集来理解它们。MNIST 是一个包含 28 x 28 像素大小的手写数字数据集,这些图像表示为 28 x 28 维度的 NumPy 数组。

每个图像都是一个数字,当前的挑战是预测图像对应的数字。

让我们下载并探索 MNIST 数据集中的一些图像,如下所示:

图片

在前面的代码片段中,我们正在导入 MNIST 对象并使用load_data函数下载 MNIST 数据集。

还要注意,load_data函数有助于自动将 MNIST 数据集分割成训练集和测试集。

让我们可视化训练集中的其中一个图像:

图片

注意,前面的数字是 5,而我们看到的网格大小为 28 x 28。

让我们进一步了解输入和输出的形状,以更好地理解数据集:

图片

由于每个输入图像的大小为 28 x 28,让我们将其展平以获取784个像素值的分数:

图片

输出层需要预测图像是否对应于 0 到 9 之间的数字之一。因此,输出层由 10 个单元组成,分别对应于 10 个不同的数字:

图片

在前面的代码中,to_categorical提供了标签的一热编码版本。

现在我们已经准备好了训练集和测试集,接下来让我们在下一节中构建神经网络的架构:

图片

注意,前面的截图中的batch_size指的是用于更新权重的数据点的数量。批量大小的直觉是:

"如果在包含 1,000 个数据点的数据集中,批量大小为 100,那么在整个数据中遍历时会有 10 次权重更新"。

注意,在预测测试集上的标签时,准确率约为 91%。

当达到 300 个 epoch 时,准确率提高至 94.9%。请注意,对于测试集上的 94.9%准确率,训练集上的准确率约为 99%。

这是一个过度拟合的经典案例,处理方法将在后续章节中讨论。

激活函数

到目前为止,我们只考虑了隐藏层中的 Sigmoid 激活函数。然而,还有许多其他激活函数在构建神经网络时非常有用。此图表提供了各种激活函数的详细信息:

图片

最常用的激活函数是 ReLU、Tanh 和逻辑或 Sigmoid 激活。

让我们探索各种激活函数在测试集上的准确率:

图片

注意,当使用 ReLU 激活时,测试集上的准确率仅为 29.75%。

然而,在进行 ReLU 激活时,在拟合模型之前对数据进行缩放总是一个好主意。缩放是一种减少输入数据集中所有值幅度的方法。

让我们先对输入进行缩放,如下所示:

图片

现在,让我们重新运行模型并查看测试数据集上的准确率:

注意,运行 10 次迭代后,测试数据集的准确率为 88.1%。现在,让我们运行模型 300 个 epoch,以便比较 sigmoid 激活和 ReLU 激活的输出。

你会注意到测试数据集的准确率为 95.76%,略高于 sigmoid 激活的准确率。然而,训练数据集的准确率为 96%,这表明它不太可能在该数据集上过拟合;因此,更多的 epoch 可能会进一步提高测试数据集的准确率。

让我们使用 Tanh 激活函数重新运行模型,首先不进行缩放,然后进行缩放。

当模型在未缩放的数据上运行时,10 个 epoch 后的准确率为 92.89%,300 个 epoch 后为 94.6%。

缩放输入数据集后,测试数据集的准确率在 10 个 epoch 后为 88%,300 个 epoch 后为 93%。

注意,当数据集缩放时,无论使用哪种激活函数,都不会出现过拟合的问题(训练数据集的准确率远高于测试数据集的准确率)。

优化器

在前一节中,我们探讨了各种激活函数,并注意到 ReLU 激活函数在高 epoch 数运行时能给出更好的结果。

在本节中,我们将探讨在激活函数保持为 ReLU 的情况下,改变优化器对缩放数据集的影响。

当运行 10 个 epoch 时,各种损失函数及其在测试数据集上的准确率如下:

优化器测试数据集准确率
SGD88%
RMSprop98.44%
Adam98.4%

现在我们已经看到 RMSprop 和 Adam 优化器比随机梯度下降优化器表现更好;让我们看看优化器内部可以修改以改进模型准确率的另一个参数——学习率。

优化器的学习率可以通过以下方式指定:

在前面的代码片段中,lr代表学习率。学习率的典型值介于 0.001 和 0.1 之间。

在 MNIST 数据集上,当我们改变学习率时,准确率没有进一步提高;然而,通常对于较低的学习率,需要更多的 epoch 才能达到相同的准确率。

增加网络的深度

增加隐藏层的深度等同于增加神经网络中隐藏层的数量。

通常,对于隐藏层中隐藏单元数量更多和/或隐藏层数量更多的情况,预测的准确率更高。

由于 Adam 优化器或 RMSprop 在经过一定数量的 epoch 后准确率会饱和,让我们切换回随机梯度下降,以了解模型运行 300 个 epoch 时的准确率;但这次我们在隐藏层中使用了更多的单元:

注意,通过在隐藏层中使用 2,000 个单元,我们的准确率在 300 个 epoch 结束时增加到 95.76%。这可能是因为输入现在可以在更高维的空间中表示,因此与 1,000 维空间场景相比,可以学习到更好的表示。

现在,我们将增加隐藏层的数量,以了解构建深度神经网络对准确率的影响:

图片

注意,当网络深度增加,有两个隐藏层而不是一个时,300 个 epoch 后的准确率为 97.24%,与单隐藏层网络相比,这是一个明显的改进。

类似地,当一层中的隐藏单元数量增加时,网络学会更复杂的数据表示,当隐藏层的数量增加时,网络也会学会更复杂的数据表示。

批量大小变化的影响

如前所述,批量大小越小,给定神经网络中权重的更新频率就越高。这导致达到网络一定准确率所需的 epoch 数量更少。同时,如果批量大小过低,网络结构可能会导致模型不稳定。

让我们在一种场景中将之前构建的网络与较小的批量大小进行比较,在下一个场景中与较大的批量大小进行比较:

图片

注意,在前面的场景中,批量大小非常高,300 个 epoch 结束时的测试数据集准确率仅为 89.91%。

这是因为批量大小为 1,024 的网络会比批量大小为 30,000 的网络更快地学习权重,因为当批量大小较小时,权重更新的数量要高得多。

在下一个场景中,我们将批量大小减少到非常小的数值,以观察对网络准确率的影响:

图片

注意,虽然准确率在 10 个 epoch 内迅速提高到 97.77%,但要产生结果需要相当长的时间,因为每个 epoch 的权重更新数量很高。这导致更多的计算,因此执行时间更长。

在 TensorFlow 中实现神经网络

在前面的章节中,我们已经了解了神经网络的工作原理以及如何在 Keras 中构建神经网络模型。在本节中,我们将致力于在 TensorFlow 中构建神经网络模型。在 TensorFlow 中构建模型有两种方式:

  • 使用预制的估计器

  • 定义自定义估计器

使用预制的估计器

预制估计器类似于 scikit-learn 等包中可用的方法,其中指定了输入特征和输出标签,以及各种超参数。然后,可以通过传递不同的函数作为参数来优化解决预定义为默认值的损失函数。

让我们探索在代码中构建训练和测试数据集:

  1. 导入相关包:

  1. 导入数据集。我们将在这个练习中使用 MNIST 数据集:

图像和标签的形状如下:

预制函数在标签值上工作,而不是在独热编码版本上。让我们将独热编码标签转换为值,如下所示:

让我们了解数据点的样子:

  1. 将数据集输入到消耗自变量(x)和因变量(y)的函数中:

注意,我们将自变量命名为 x2,因变量命名为 y

此外,请注意,我们已经传递了形成自变量和因变量值的数组。

batch_size 表示用于计算损失函数的训练示例数量,而 num_epochs = None 表示稍后将会提供要运行的 epoch 数量。

train_input_fn 返回特征和标签,如下所示:

同样,我们传递测试数据集:

注意,在测试数据集的情况下,num_epochs = 1,因为我们只通过前馈传递一次测试数据集,一旦从训练中推导出模型权重。

一个数据集可能包含多个列,因此让我们指定特征列及其类型,如下所示:

如果有多个列,我们将在列表中指定所有列,如下所示:

feature_columns = [feature_x1, feature_x2]

feature_x1 是一个特征,而 feature_x2 是另一个特征。

现在,我们将指定隐藏层的数量以及每层的隐藏单元数:

注意,通过以上方式指定隐藏单元数,我们已经指定了有三个隐藏层,其中第一个隐藏层有 512 个单元,第二个隐藏层有 256 个单元,最后一个隐藏层有 128 个单元。

既然我们已经指定了特征和隐藏层,让我们指定神经网络架构,如下所示:

既然我们已经指定了模型架构,我们可以继续训练模型。如果您想进一步更改函数中可用的超参数,您可以通过使用 help 函数查看可用的超参数调节器,如下所示:

以下代码运行神经网络模型总共 2,000 个 epoch:

图片

现在我们已经运行了模型,让我们评估测试数据集上的准确率,如下所示:

图片

我们可以看到,模型在测试数据集上的准确率为 97.2%。

到目前为止,我们一直在使用预制评估器实现模型;在接下来的章节中,我们将探讨在不使用预制评估器的情况下定义模型。

创建自定义评估器

预制评估器限制了 TensorFlow 可以发挥的全面潜力;例如,我们无法在各个层之后有不同的 dropout 值。在这方面,让我们继续创建一个我们自己的函数,如下所示:

图片

让我们详细探索前面代码片段的每个部分:

图片

该函数接受特征(自变量)和标签(因变量)作为输入。mode表示我们想要训练、预测还是评估给定数据。

params为我们提供了提供有关参数信息的功能;例如,学习率:

图片

前面的代码片段类似于我们在 Keras 中定义模型架构的方式,其中我们指定了输入、隐藏层激活函数和隐藏层中的单元数:

图片

如果我们的模式是预测类别,我们就不需要训练模型,只需传递预测的类别,因此在这种情况下评估器规范只需计算y_pred_cls值,因此以下代码:

图片

如果模式是训练或测试模型,我们就必须计算损失,因此以下代码:

图片

在前面的代码中,第一行用于定义交叉熵计算。第二行计算所有行交叉熵的平均值。

optimizer指定我们感兴趣的优化器和学习率。train_op指定我们感兴趣的是最小化损失,而global_step参数记录模型当前所在的步(epoch)。metrics指定我们感兴趣计算的指标,最终计算的spec将是之前定义的所有参数的组合。

一旦定义了模型架构和需要返回的评估器规范,我们定义参数和模式如下:

图片

从前面的代码中,函数学习需要更改的参数以及需要工作的模型架构(model_fn):

图片

我们通过指定模式(在这种情况下为train)和一定的训练轮数(在这种情况下为2000)来运行模型。

运行模型后,我们评估模型在测试数据集上的准确度,如下所示:

图片

摘要

在本章中,我们学习了如何设置 Datalab 在 Google Cloud 上执行神经网络。我们还学习了神经网络的架构以及各种参数,如深度、隐藏单元数量、激活函数、优化器、批量大小和训练轮数如何影响模型的准确度。我们还看到了如何在 Keras 和 TensorFlow 中实现神经网络。本章还涵盖了使用预制的估计器和在 TensorFlow 中创建自定义估计器等主题。