21 循环神经网络和其他深度学习模型
在本章中,我们将学习深度学习和循环神经网络(RNN)。 像前几章所介绍的 CNN 一样,RNN 在过去几年中也获得了很大发展。 就 RNN 而言,它们在语音识别领域被大量使用。 当今许多聊天机器人都基于 RNN 技术建立了基础。 使用 RNN 预测金融市场已经取得了一些成功。 例如,我们可能有一个带有单词序列的文本,并且我们有一个目标来预测序列中的下一个单词。
我们将讨论 RNN 的架构及其组件。 我们将继续使用上一章开始学习的 TensorFlow。 我们将使用 TensorFlow 快速构建 RNN。 我们还将学习如何使用单层神经网络构建 RNN 分类器。 然后,我们将使用 CNN 构建图像分类器。
到本章末,您将了解:
- RNN 的基础
- RNN 的架构
- RNN 中的层类型
- 语言建模用例的详细信息
- 如何使用基本算法构建初始 RNN 分类器
- 如何使用更先进的技术来增强 RNN
让我们从 RNN 的基础开始。
循环神经网络的基础
RNN 是另一种流行的模型,目前正在获得很大的关注。 正如我们在第 1 章,“人工智能导论”中讨论的那样,对一般神经网络(尤其是 RNN)的研究是连接主义者流派(在 Pedro Domingos 的 AI 分类中进行了介绍)。 RNN 通常用于解决自然语言处理(NLP)和自然语言理解(NLU)问题 。
RNN 背后的数学有时可能会令人不知所措。 在深入研究 RNN 之前,请牢记以下思想:赛车手不需要完全了解其赛车的机械原理即可使其快速行驶并赢得比赛。 同样,我们不一定需要完全了解 RNN 在幕后的工作方式,以使其对我们有用,有时甚至是令人印象深刻的工作。 Keras 库的创建者 Francois Chollet 描述了长短期记忆(LSTM)网络,这是 RNN 的一种形式,如下所示:
“您不需要了解有关 LSTM 单元的特定架构的所有内容;作为人类,理解它不是您的工作。只需记住 LSTM 单元的含义:允许过去的信息以后再注入。”
现在让我们进入神经网络的一些基础知识。 顾名思义,神经网络从大脑神经元的结构中获得灵感。 神经网络中的神经元大致模拟了人类神经元的结构和功能。 对于一般的大脑,尤其是神经元,我们有很多不了解的地方。 但在基本级别上,神经元接受输入,并且如果达到阈值,它将触发输出。 用数学术语来说,人工神经元是数学函数的容器,其唯一的任务是通过将给定的输入应用于函数来传递输出。
既然我们已经了解了使神经网络滴答作响的原因以及使它们发火的原因,那么让我们了解一些用于触发神经网络的常用函数。 这些函数通常称为激活函数,因为它们在达到阈值时就会激活。 您可以将任何类型的函数用作激活函数,但以下是一些用作激活函数的常用函数:
- 步进函数
- Sigmoid 函数
- tanh 函数
- ReLU 函数
以下各节将更详细地描述每一个。
步进函数
步进函数是简单函数。 就这么简单。 如果输出高于某个阈值,则会触发该函数。 否则就不会。 图形化:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0AZZBh3w-1681568892770)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_01.png)]
图 1:单位步进函数
如果x
的值大于或等于零,则输出为1
,如果x
的值小于0
。 零。 可以看出,阶跃函数不可微为零。 神经网络通常使用反向传播和梯度下降来计算不同层的权重。 由于阶跃函数为零时不可微,因此它无法向下进行梯度下降,并且在尝试更新其权重时会失败。
为了克服这个问题,我们可以改用 Sigmoid 函数。
Sigmoid 函数
Sigmoid 函数(也称为作为逻辑函数)定义如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8L0fTlIT-1681568892770)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_02.png)]
图 2:Sigmoid 函数
当z
(自变量)趋于负无穷大时,函数的值趋于0
;当z
趋于趋近时,函数的值趋于1
到无穷远。
Sigmoid 函数有一个缺点。 容易出现梯度消失的问题。 从图形中可以看出,Sigmoid 函数的值在 0 到 1 之间的较小范围内。Sigmoid 函数具有陡峭的梯度。 因此,在许多情况下,输入的大变化会导致输出的小变化。 此问题称为梯度消失。 随着网络中层数的增加,该问题呈指数增长,因此很难扩展使用此函数的神经网络。
使用 Sigmoid 函数的原因之一是因为其输出始终落在 0 到 1 之间。因此,它对于需要预测输出为概率的模型很有用。 概率总是在 0 到 1 的范围内。因此,在这些情况下,Sigmoid 是要使用的适当函数。
现在我们来看 tanh 函数,它克服了 Sigmoid 函数的一些问题。
Tanh 函数
tanh(z)
函数是 Sigmoid 函数的重新缩放的版本。 其输出范围是-1
至-1
,而不是0
至1
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Yi6dpXr-1681568892771)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_03.png)]
图 3:Tanh 函数
之所以使用 tanh 函数而不是 Sigmoid 函数的主要原因是因为值以 0 为中心,所以导数更高。 较高的梯度有助于产生更好的学习率,因此可以更快地训练模型。 但是,使用 tanh 函数时,仍然存在梯度消失问题。
现在,我们将了解另一个函数:ReLU 函数。
ReLU 函数
整流线性单元(ReLU)函数可能是在 CNN 和 RNN 模型中最流行的激活函数。 给定负输入时,该函数返回 0。 给定任何正值时,它将返回该值。 因此,它可以写为:
代码语言:javascript复制f(x) = max(0, x)
图形上看起来像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqmOZ0dL-1681568892771)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_04.png)]
图 4:ReLU 函数
在 ReLU 变体中,LReLU 实现是最受欢迎的实现之一。 对于正数,它返回与常规 ReLU 相同的值。 但是,它没有返回0
为负值,而是具有恒定的斜率(小于1
)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sm6lgMZS-1681568892772)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_05.png)]
图 5:LReLU 函数
该斜率是函数用户在设置模型时设置的参数。 斜率由α
标识。 例如,对于α = 0.3,激活函数为:
f(x) = max(0.3 * x, x)
LReLU 具有理论上的优势,即在所有值下都受到x
的影响,因此可以更好地利用所有输入提供的信息。
鉴于 ReLU 的特性和优势,它经常是深度学习从业人员和研究人员选择的激活函数。
现在,我们已经介绍了 RNN 的一些基本功能,并讨论了它们的一些关键功能,下面让我们深入研究其实际的架构。
RNN 的架构
RNN 背后的主要概念是利用序列中的先前信息。 在传统的神经网络中,假定所有输入和输出彼此独立。 在某些领域和用例中,这种假设是不正确的,我们可以利用这种相互联系的优势。
我将以个人为例。 我相信,在很多情况下,我可以根据几个最初的句子来预测我妻子接下来会说些什么。 我倾向于相信自己的预测能力具有很高的准确率。 也就是说,如果您问我的妻子,她可能会告诉您一个完全不同的故事! Google 的电子邮件服务 Gmail 正在使用类似的概念。 如果您是该服务的用户,您会注意到,从 2019 年开始,它在认为可以完成句子时就开始提出建议。 如果猜对了,则只需按 Tab 键即可完成句子。 如果没有,您可以继续输入,并且可能会根据新输入给出不同的建议。 我不了解此服务的实现内部原理,但是可以假定他们正在使用 RNN 技术,因为 RNN 非常善于处理此类问题。
RNN 之所以称为循环,是因为这些算法对序列的每个元素执行相同的任务,并且输出取决于先前的计算。 您也可以将 RNN 视为具有“内存”,用于存储有关到目前为止发生的事情和已经计算出的信息。 从理论上讲,RNN 能够从中提取信息的序列长度没有限制。 实际上,它们通常以仅回顾几步的方式实现。 这是通常用于表示 RNN 的图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RALFKvJ-1681568892772)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_06.png)]
图 6:循环神经网络及其正向计算所涉及的计算时间的展开
资料来源:LeCun,Bengio 和 G. Hinton,2015 年,《深度学习》,《自然》
上图表示作为完整网络展开或展开的 RNN。 术语展开用来表示逐步为整个序列布置网络。 例如,如果将前三个单词用于预测下一个单词,则将网络展开为 3 层网络,每个单词一层。 控制 RNN 中发生的计算的公式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWL9lr71-1681568892772)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_07.png)]
图 7:3 层循环神经网络
资料来源:LeCun,Bengio 和 G. Hinton,2015 年,《深度学习》,《自然》
x[t]
是时间步长t
的输入。 在这种情况下,x[1]
可能是单热门属性,与句子中的第 2 个单词相对应。
s[t]
是时间步长t
的隐藏状态。 您可以将其视为网络的记忆。s[t]
是使用先前的隐藏状态和当前步骤的输入来计算的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I16Zj0Tq-1681568892772)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_001.png)]
最常用的函数f
是非线性函数,例如 tanh 或 ReLU。 通常,将计算第 1 个隐藏状态所需的s-1
初始化为零。
o[t]
是步骤t
的输出。 例如,如果我们想预测句子中的下一个单词,那么它将是整个词汇表中概率的向量:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H8qsM04P-1681568892773)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_002.png)]
这里有一些注意事项。 您可以将隐藏状态s[t]
视为网络的内存。s[t]
捕获有关在所有先前时间步中发生的情况的信息。 仅根据时间t
时的内存计算步骤o[t]
的输出。 如前所述,由于s[t]
只能捕获有关有限数量先前步骤的信息,因此它在实践中更为复杂。
与传统的深度神经网络在每一层使用不同的参数不同,RNN 共享相同的参数(先前显示的U
,V
和W
) 所有步骤。 这是因为每个步骤都执行相同的任务,但是输入不同。 这大大减少了需要跟踪的参数总数。
上一个图具有每个时间步的输出,但是根据任务的不同,可能不需要此输出。 例如,执行情感分析时,通常对整个句子的情感而不是每个单词感兴趣。 同样,我们可能不需要每个时间步都输入。 RNN 的主要特征是其隐藏状态,该状态捕获有关序列的一些信息。
现在是时候深入研究一个具体的例子了。 我们将学习如何使用 RNN 预测句子中的下一个单词。 让我们做一些预测。
语言建模用例
我们的目标是使用 RNN 建立语言模型。 这就是这个意思。 假设我们有一个m
个单词的句子。 语言模型使我们能够预测观察句子(在给定数据集中)的概率为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EQRmISYW-1681568892773)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_003.png)]
换句话说,句子的概率是给定单词前面每个单词的概率的乘积。 因此,句子“请让我知道是否有任何问题”的概率等于给出“请让我知道如果您有…的问题”的概率乘以给出“有”的概率“ 请让我知道您是否有…”等等。
这有什么用? 为什么将概率分配给给定句子很重要?
首先,像这样的模型可以用作评分机制。 语言模型可用于选择最可能的下一个单词。 从直觉上讲,最可能出现的下一个单词在语法上可能是正确的。
语言建模具有重要应用。 因为它可以预测给定前面单词的单词的概率,所以可以将其用于自然文本生成(NTG)。 给定一个现有的单词序列,从概率最高的单词列表中建议一个单词,然后重复该过程,直到生成完整的句子为止。
注意,在前面的等式中,每个单词的概率以所有先前的单词为条件。 在更实际的情况下,由于计算或内存限制,模型可能很难代表长期依赖关系。 因此,大多数模型通常仅限于仅查看少量的先前单词。
足够的理论。 现在,我们准备开始编写一些代码,并学习如何训练 RNN 以生成文本。
RNN 训练
正如我们在本章的开头所讨论的那样,RNN 的应用在众多行业中广泛而多样。 在我们的案例中,我们仅执行一个简单的示例,以便更牢固地理解 RNN 的基本机制。
我们将尝试使用 RNN 建模的输入数据是数学余弦函数。
因此,首先让我们定义输入数据并将其存储到 NumPy 数组中。
代码语言:javascript复制import numpy as np
import math
import matplotlib.pyplot as plt
input_data = np.array([math.cos(x) for x in np.arange(200)])
plt.plot(input_data[:50])
plt.show
前面的语句将绘制数据,以便我们可以可视化输入数据的外观。 您应该得到如下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZlcpYP4-1681568892773)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_09.png)]
图 8:可视化输入数据
现在,将输入数据分为两组,以便我们可以将一部分用于训练,另一部分用于验证。 从训练的角度看,这也许不是最佳的分割方法,但是为了使事情变得简单,让我们将数据分割到中间。
代码语言:javascript复制X = []
Y = []
size = 50
number_of_records = len(input_data) - size
for i in range(number_of_records - 50):
X.append(input_data[i:i size])
Y.append(input_data[i size])
X = np.array(X)
X = np.expand_dims(X, axis=2)
Y = np.array(Y)
Y = np.expand_dims(Y, axis=1)
让我们打印得到的训练数组的形状:
代码语言:javascript复制X.shape, Y.shape
您应该看到如下输出:
代码语言:javascript复制((100, 50, 1), (100, 1))
让我们创建验证集:
代码语言:javascript复制X_valid = []
Y_valid = []
for i in range(number_of_records - 50, number_of_records):
X_valid.append(input_data[i:i size])
Y_valid.append(input_data[i size])
X_valid = np.array(X_valid)
X_valid = np.expand_dims(X_valid, axis=2)
Y_valid = np.array(Y_valid)
Y_valid = np.expand_dims(Y_valid, axis=1)
接下来,让我们定义 RNN 将使用的参数。 例如,我们定义隐藏层以包含100
单元:
learning_rate = 0.0001
number_of_epochs = 5
sequence_length = 50
hidden_layer_size = 100
output_layer_size = 1
back_prop_truncate = 5
min_clip_value = -10
max_clip_value = 10
让我们定义各层之间连接的权重:
代码语言:javascript复制W1 = np.random.uniform(0, 1, (hidden_layer_size, sequence_length))
W2 = np.random.uniform(0, 1, (hidden_layer_size, hidden_layer_size))
W3 = np.random.uniform(0, 1, (output_layer_size, hidden_layer_size))
在前面的代码中:
W1
是输入层和隐藏层之间权重的权重矩阵W2
是隐藏层和输出层之间权重的权重矩阵W3
是 RNN 层(隐藏层)中共享权重的权重矩阵
我们将用于 RNN 的激活函数是 Sigmoid 函数。 有关一般的激活函数,尤其是 Sigmoid 函数的详细讨论,请参见上一章。
代码语言:javascript复制def sigmoid(x):
return 1 / (1 np.exp(-x))
现在,我们已经准备就绪,可以开始训练模型了。 我们将迭代25
周期。 您将在结果中清楚地看到模型和实际数据开始收敛的点。 确保收敛后停止训练。 否则,我们将过拟合数据,并且我们的模型将使用训练数据生成良好的数字,但对于尚未看到的数据将不会表现良好。
运行该程序几次。 一旦看到数据开始收敛,就可以调整“周期数”的值。
这是训练期间将执行的步骤的概述:
- 检查训练数据上的损失
- 执行正向传播
- 计算误差
- 检查验证数据上的损失
- 执行正向传播
- 计算误差
- 开始训练
- 执行正向传播
- 反向传播误差
- 更新权重
for epoch in range(number_of_epochs):
# check loss on train
loss = 0.0
# do a forward pass to get prediction
for i in range(Y.shape[0]):
x, y = X[i], Y[i]
prev_act = np.zeros((hidden_layer_size, 1))
for t in range(sequence_length):
new_input = np.zeros(x.shape)
new_input[t] = x[t]
mul_w1 = np.dot(W1, new_input)
mul_w2 = np.dot(W2, prev_act)
add = mul_w2 mul_w1
act = sigmoid(add)
mul_w3 = np.dot(W3, act)
prev_act = act
# calculate error
loss_per_record = (y - mul_w3)**2 / 2
loss = loss_per_record
loss = loss / float(y.shape[0])
# check loss on validation
val_loss = 0.0
for i in range(Y_valid.shape[0]):
x, y = X_valid[i], Y_valid[i]
prev_act = np.zeros((hidden_layer_size, 1))
for t in range(sequence_length):
new_input = np.zeros(x.shape)
new_input[t] = x[t]
mul_w1 = np.dot(W1, new_input)
mul_w2 = np.dot(W2, prev_act)
add = mul_w2 mul_w1
act = sigmoid(add)
mul_w3 = np.dot(W3, act)
prev_act = act
loss_per_record = (y - mul_w3)**2 / 2
val_loss = loss_per_record
val_loss = val_loss / float(y.shape[0])
print('Epoch: ', epoch 1, ', Loss: ', loss, ', Val Loss: ', val_loss)
# train model
for i in range(Y.shape[0]):
x, y = X[i], Y[i]
layers = []
prev_act = np.zeros((hidden_layer_size, 1))
dW1 = np.zeros(W1.shape)
dW3 = np.zeros(W3.shape)
dW2 = np.zeros(W2.shape)
dW1_t = np.zeros(W1.shape)
dW3_t = np.zeros(W3.shape)
dW2_t = np.zeros(W2.shape)
dW1_i = np.zeros(W1.shape)
dW2_i = np.zeros(W2.shape)
# forward pass
for t in range(sequence_length):
new_input = np.zeros(x.shape)
new_input[t] = x[t]
mul_w1 = np.dot(W1, new_input)
mul_w2 = np.dot(W2, prev_act)
add = mul_w2 mul_w1
act = sigmoid(add)
mul_w3 = np.dot(W3, act)
layers.append({'act':act, 'prev_act':prev_act})
prev_act = act
# derivative of pred
dmul_w3 = (mul_w3 - y)
# backward pass
for t in range(sequence_length):
dW3_t = np.dot(dmul_w3, np.transpose(layers[t]['act']))
dsv = np.dot(np.transpose(V), dmul_w3)
ds = dsv
dadd = add * (1 - add) * ds
dmul_w2 = dadd * np.ones_like(mul_w2)
dprev_act = np.dot(np.transpose(W2), dmul_w2)
for i in range(t-1, max(-1, t-back_prop_truncate-1), -1):
ds = dsv dprev_act
dadd = add * (1 - add) * ds
dmul_w2 = dadd * np.ones_like(mul_w2)
dmul_w1 = dadd * np.ones_like(mul_w1)
dW2_i = np.dot(W2, layers[t]['prev_act'])
dprev_act = np.dot(np.transpose(W2), dmul_w2)
new_input = np.zeros(x.shape)
new_input[t] = x[t]
dW1_i = np.dot(U, new_input)
dx = np.dot(np.transpose(U), dmul_w1)
dW1_t = dW1_i
dW2_t = dW2_i
dW3 = dW3_t
dW1 = dW1_t
dW2 = dW2_t
if dW1.max() > max_clip_value:
dW1[dW1 > max_clip_value] = max_clip_value
if dW3.max() > max_clip_value:
dW3[dW3 > max_clip_value] = max_clip_value
if dW2.max() > max_clip_value:
dW2[dW2 > max_clip_value] = max_clip_value
if dW1.min() < min_clip_value:
dW1[dW1 < min_clip_value] = min_clip_value
if dW3.min() < min_clip_value:
dW3[dW3 < min_clip_value] = min_clip_value
if dW.min() < min_clip_value:
dW2[dW2 < min_clip_value] = min_clip_value
# update
W1 -= learning_rate * dW1
W3 -= learning_rate * dW3
W2 -= learning_rate * dW2
输出应该看起来像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nMIQL6sk-1681568892774)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_11.png)]
图 9:5 个周期的 RNN 训练输出
如您所见,损失和验证损失在每个周期都在不断减少。 我们可能会再运行几个模型,并确保结果收敛。
这是一个有 10 个历时的示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWshDakx-1681568892774)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_12.png)]
图 10:10 个周期的 RNN 训练输出
如您所见,在第 6 个阶段之后,结果已经收敛。 找出最佳周期数是一个试错过程。 从这些结果可以看出,大约 6 个周期是正确的周期数。
在这个例子中,我们几乎已经走到了尽头。 让我们根据预测值绘制初始输入数据集,然后看看如何得出。 以下是相关代码:
代码语言:javascript复制preds = []
for i in range(Y_valid.shape[0]):
x, y = X_valid[i], Y_valid[i]
prev_act = np.zeros((hidden_layer_size, 1))
# For each time step...
for t in range(sequence_length):
mul_w1 = np.dot(W_1, x)
mul_w2 = np.dot(W2, prev_act)
add = mul_w2 mul_w1
act = sigmoid(add)
mul_w3 = np.dot(W3, act)
prev_act = act
preds.append(mul_w3)
preds = np.array(preds)
plt.plot(preds[:, 0, 0], 'g')
plt.plot(Y_valid[:, 0], 'r')
plt.show()
这是的结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtZu8lxi-1681568892774)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_21_13.png)]
图 11:RNN 训练预测与实际图
如我们所见,预测值与初始值是相当一致的,因此这是一个不错的结果。 RNN 本质上是随机的,因此下一次我们运行示例时,对齐方式可能会更好或更坏。 您可能会看到不同的对齐方式。
最后,让我们计算均方根误差(RMSE)分数:
代码语言:javascript复制from sklearn.metrics import mean_squared_error
math.sqrt(mean_squared_error(Y_valid[:, 0], preds[:, 0, 0]))
RMSE 是方差的平方根。 可以将其解释为无法解释的方差的标准差。 它具有与响应变量具有相同单位的有用属性。 RMSE 值越低表示拟合度越好。
在这种情况下,我们得到的分数很低,表明我们的模型正在产生不错的预测:
代码语言:javascript复制0.5691944360057564
这是一个实际的 RNN 的简单示例。 而且,如您所见,这不是简单的两行程序! 这凸显了一个事实,即 RNN 实现起来并不简单,将其应用于复杂任务是一项真正的任务。
此外,RNN 的计算量很大,因此生成准确的预测可能会花费大量时间。 如果我们无法使用合适的硬件,那几乎是肯定的。
尽管如此,RNN 已经取得了许多突破,并且数据科学界继续不断地为其寻找新的应用。 不仅如此,它们的表现和准确率也在不断提高。 到目前为止,我们将提高您已经设计的神经网络的表现和功能,并提出您自己的示例,说明如何将网络应用于其他领域。 快乐的编码。
总结
在本章中,我们继续学习深度学习并学习了 RNN 的基础。 然后,我们讨论了 RNN 架构的基本概念是什么,以及为什么这些概念很重要。 在学习了基础知识之后,我们研究了 RNN 的一些潜在用途,并着眼于使用它来实现语言模型。 最初,我们使用基本技术来实现语言模型,然后开始为模型添加越来越多的复杂性以理解更高层次的概念。
希望您和我们一样兴奋,进入下一章,我们将学习如何使用强化学习来创建智能体。
22 通过强化学习创建智能体
在本章中,我们将学习强化学习(RL)。 我们将讨论 RL 的前提。 我们将讨论 RL 和监督学习之间的区别。 我们将通过一些真实的 RL 实例来了解 RL 如何以各种形式表现出来。 我们将学习 RL 的组成部分以及所涉及的各种概念。 然后,我们将在 Python 中创建一个环境,以了解其在实际中的工作方式。 然后,我们将使用这些概念来构建学习智能体。
在本章中,我们将介绍以下主题:
- 了解学习的意义
- 强化学习与监督学习
- RL 的真实示例
- RL 的构成部分
- 创建环境
- 构建学习智能体
在进入 RL 本身之前,我们首先考虑一下学习的真正含义。 毕竟,它将帮助我们在尝试实现之前理解它!
了解学习的意义
学习的概念是人工智能的基础。 我们希望机器了解学习的过程,以便他们可以自己完成学习。 人类通过观察周围环境并与周围环境互动来学习。 当您去一个新地方时,您可以快速扫描并查看周围发生的事情。
没有人在教你怎么做。 您正在观察周围的环境并与之互动。 通过与环境建立这种联系,我们倾向于收集有关导致不同原因的大量信息。 我们了解因果关系,哪些行动会导致什么结果以及我们需要做些什么才能实现目标。
我们在生活中的每一个地方都使用这种学习过程。 我们收集了有关周围环境的所有知识,然后了解我们如何对此做出反应。 让我们考虑一个演说家的例子。 每当好的演说家在公开场合发表演讲时,他们就会知道人群对他们的发言有何反应。 如果人群没有响应,那么演说者会实时更改语音以确保人群参与。 如我们所见,演说者正试图通过他/她的行为来影响环境。 可以说,演说者从与人群的互动中学习,以便采取行动并达到一定的目标。 这种学习过程–观察环境,采取行动,评估该行动的后果,适应和再次采取行动–是人工智能中许多主题所基于的最基本的思想之一。 让我们牢记这一点来谈论 RL。
RL 是指学习操作方法并将情况映射到某些动作以最大化回报的过程。 在大多数机器学习范式中,学习智能体会被告知要采取哪些行动才能获得一定的结果。 在加强学习的情况下,不会告知学习智能体要采取什么行动。 取而代之的是,它必须通过尝试找出哪些行动会产生最高的回报。 这些行动往往会影响立即的报酬以及下一个情况。 这意味着所有后续奖励也将受到影响。
思考 RL 的一种好方法是理解我们定义的是学习问题而不是学习方法。 因此,可以说可以解决问题的任何方法都可以视为 RL 方法。 RL 的两个显着特征-反复试验学习和延迟奖励。 RL 智能体使用这两个功能从中学习其行为的后果。
强化学习与监督学习
当前许多研究都集中在监督学习上。 RL 似乎有点像监督学习,但事实并非如此。 监督学习的过程是指从标记的样本中学习。 尽管这是一种有用的技术,但不足以从交互中开始学习。 当我们想设计一台机器来导航未知的地形时,这种学习将无济于事。 我们事先没有可用的训练样本。
我们需要一个可以通过与未知地形交互来从自身经验中学习的智能体。 这是 RL 真正闪耀的地方。
让我们考虑一下智能体与新环境进行交互以进行学习时的探索阶段。 它可以探索多少? 在这一点上,该智能体不知道环境有多大,并且在许多情况下,它将无法探索所有可能性。 那么,智能体应该做什么? 它是应该从其有限的经验中吸取教训,还是等到进一步探索后再采取行动? 这是 RL 的主要挑战之一。 为了获得更高的报酬,智能体必须支持经过尝试和测试的行为。 但是为了发现此类动作,它应继续尝试以前未选择的较新动作。 多年来,研究人员已经广泛研究了在探索与开发之间的这种权衡方案,但它仍然是活跃的话题。
强化学习的实际示例
让我们看看 RL 在现实世界中出现的位置。 这将帮助我们了解它的工作原理以及使用此可以构建哪些可能的应用,除非:
游戏:让我们考虑一下棋类游戏,例如围棋或象棋。 为了确定最佳动作,玩家需要考虑各种因素。 可能性的数量如此之大,以至于无法进行暴力搜索。 如果我们要使用传统技术制造一台可以玩这种游戏的机器,则需要指定许多规则来涵盖所有这些可能性。 RL 完全绕过了这个问题。 我们不需要手动指定任何逻辑规则。 学习智能体仅通过示例学习并与自己玩游戏。
有关此主题的更详尽讨论,请参阅第 2 章,“人工智能基本用例”中的“游戏”部分。
机器人技术:让我们考虑一个机器人的工作,该机器人的工作是探索新建筑物。 它应确保它有足够的功率返回基站。 该机器人必须通过考虑所收集的信息量与安全返回基站的能力之间的权衡来决定是否应该做出决定。
有关此主题的更多信息,请跳至第 2 章,“人工智能基本用例”中的“装运和仓库管理”部分。
工业控制器:考虑调度电梯的情况。 一个好的调度器将花费最少的电量,并为最多的人员服务。 对于此类问题,RL 智能体可以学习如何在模拟环境中执行此操作。 然后,他们可以利用这些知识来制定最佳计划。
婴儿:机器在使用 RL 方面没有垄断权; 刚开始的几个月里,新生儿经历了几乎相同的过程。 他们通过反复尝试来学习,直到学会平衡为止。 有趣的是,婴儿发现了走路(或跑步!)是最有效的方式之后,发现了不同的运动方法。
如果仔细观察这些示例,您将看到一些共同的特征。 所有这些都涉及与环境的交互。 即使环境不确定,学习智能体(无论是机器,婴儿还是其他设备)也旨在实现某个目标。 智能体的动作将改变该环境的未来状态。 随着智能体继续与环境交互,这会在以后的时间影响可用的机会。
在讨论了 RL 是什么之后,并涵盖了一些真实的示例,现在让我们开始研究 RL。 我们将从讨论 RL 系统的构建块开始。
强化学习的基础
除了智能体与环境之间的交互,RL 系统中还有其他因素在起作用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sBPl6jaD-1681568892774)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_01.png)]
图 1:RL 的构成
通常,RL 智能体执行以下步骤:
- 有一组与智能体和环境有关的状态。 在给定的时间点,智能体会观察输入状态以感知环境。
- 策略规定需要采取什么措施。 这些策略充当决策函数。 使用这些策略根据输入状态确定操作。
- 智能体根据上一步采取行动。
- 环境对此动作做出反应。 智能体从环境中获得强化,也称为奖励。
- 智能体计算并记录有关该奖励的信息。 重要的是要注意,将为此状态/动作对收到此奖励,以便在给定特定状态的情况下,将来可将其用于采取更多的奖励行动。
RL 系统可以同时执行多个事情–通过执行试错搜索来学习,了解所处环境的模型,然后使用该模型来计划下一步。 在下一节中,我们将动手操作,并开始使用流行的框架使用 Python 编写的强化学习系统。
创建环境
我们将使用名为 OpenAI Gym 的包来构建 RL 智能体。 您可以在此处了解更多有关的信息。 可以通过运行以下命令使用pip
进行安装:
$ pip3 install gym
您可以在此处找到与其安装相关的各种提示和技巧。
现在您已经安装了它,让我们继续编写一些代码。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import argparse
import gym
定义一个函数来解析输入参数。 输入参数将用于指定要运行的环境的类型:
代码语言:javascript复制def build_arg_parser():
parser = argparse.ArgumentParser(description='Run an environment')
parser.add_argument('--input-env', dest='input_env', required=True,
choices=['cartpole', 'mountaincar', 'pendulum', 'taxi', 'lake'],
help='Specify the name of the environment')
return parser
定义main
函数并解析输入参数:
if __name__=='__main__':
args = build_arg_parser().parse_args()
input_env = args.input_env
创建一个从输入参数字符串到 OpenAI Gym 包中指定的环境名称的映射:
代码语言:javascript复制 name_map = {'cartpole': 'CartPole-v0',
'mountaincar': 'MountainCar-v0',
'pendulum': 'Pendulum-v0',
'taxi': 'Taxi-v1',
'lake': 'FrozenLake-v0'}
根据输入参数创建环境,并将其重置:
代码语言:javascript复制 # Create the environment and reset it
env = gym.make(name_map[input_env])
env.reset()
重复1000
次并在每个步骤中随机执行一个操作:
# Iterate 1000 times
for _ in range(1000):
# Render the environment
env.render()
# take a random action
env.step(env.action_space.sample())
完整代码在文件run_environment.py
中给出。 如果您想知道如何运行代码,请使用help
参数运行它,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csCseh9a-1681568892775)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_02.png)]
图 2:运行 Python 程序的命令
让我们在非常规环境下运行它。 运行以下命令:
代码语言:javascript复制$ python3 run_environment.py --input-env cartpole
如果运行它,您将看到一个窗口,其中显示杆子向右移动。 以下屏幕截图显示了初始位置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKEVD1RM-1681568892775)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_03.png)]
图 3:Cartpole 示例输出
在下一秒钟左右,您将看到它移动,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FlT5fLuq-1681568892776)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_04.png)]
图 4:Cartpole 示例输出 2
在端,您将看到它从窗口中消失,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1TEPQqbz-1681568892776)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_05.png)]
图 5:Cartpole 示例输出 3
让我们使用mountaincar
参数运行它。 运行以下命令:
$ python3 run_environment.py --input-env mountaincar
如果运行代码,则最初将看到下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G4kphdbx-1681568892776)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_06.png)]
图 6:山地车示例输出
如果让它运行几秒钟,您会看到汽车摆动得更多,从而达到了标志:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t8TazCVL-1681568892776)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_07.png)]
图 7:山地车示例输出 2
它将保持更大的步幅,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-egTN6KTj-1681568892777)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_08.png)]
图 8:山地车示例输出 3
在本节的第一个示例中,没有发生任何令人兴奋的事情。 它只是四处走动。 该示例确实使我们对所使用的 RL 框架有了基本的了解。 第二个示例更令人兴奋。 它实际上有一个目标(触摸标志)。 实现目标通常是如何构造强化问题。 在下一个示例中,我们将继续使事情变得更加有趣,并将我们的目光投向一个稍微复杂的目标。
建立学习智能体
在本节的中,我们将基于第一个例子进行研究。 最初,柱子只是在四处移动。 现在,我们将尝试平衡购物车顶部的电线杆,并确保电线杆保持直立。 准备学习更多吗? 让我们开始吧。
首先,创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import argparse
import gym
定义一个函数来解析输入参数:
代码语言:javascript复制def build_arg_parser():
parser = argparse.ArgumentParser(description='Run an environment')
parser.add_argument('--input-env', dest='input_env', required=True,
choices=['cartpole', 'mountaincar', 'pendulum'],
help='Specify the name of the environment')
return parser
解析输入参数:
代码语言:javascript复制if __name__=='__main__':
args = build_arg_parser().parse_args()
input_env = args.input_env
在 OpenAI Gym 包中建立从输入参数到环境名称的映射:
代码语言:javascript复制 name_map = {'cartpole': 'CartPole-v0',
'mountaincar': 'MountainCar-v0',
'pendulum': 'Pendulum-v0'}
根据输入参数创建环境:
代码语言:javascript复制 # Create the environment
env = gym.make(name_map[input_env])
通过重置环境开始迭代:
代码语言:javascript复制 # Start iterating
for _ in range(20):
# Reset the environment
observation = env.reset()
对于每次复位,迭代100
次。 首先渲染环境:
# Iterate 100 times
for i in range(100):
# Render the environment
env.render()
打印当前观察值并根据可用的操作空间执行操作:
代码语言:javascript复制 # Print the current observation
print(observation)
# Take action
action = env.action_space.sample()
提取采取当前措施的后果:
代码语言:javascript复制 # Extract the observation, reward, status and
# other info based on the action taken
observation, reward, done, info = env.step(action)
检查我们是否实现了目标:
代码语言:javascript复制 # Check if it's done
if done:
print('Episode finished after {} timesteps'.format(i 1))
break
完整代码在文件balancer.py
中给出。 如果您想知道如何运行代码,请使用help
参数运行它,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PG3IBOZn-1681568892777)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_09.png)]
图 9:运行 Python 平衡器示例的命令
让我们在cartpole
环境中运行代码。 运行以下命令:
$ python3 balancer.py --input-env cartpole
如果运行代码,您将看到杆子平衡自身:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10M6mEGH-1681568892777)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_10.png)]
图 10:Cartpole 示例输出 4
如果让它运行几秒钟,您将看到杆子仍然站立,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRHsBPFD-1681568892777)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_11.png)]
图 11:Cartpole 示例输出 5
您应该会看到许多打印出来的信息。 如果您看其中的一集,它将看起来像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zVUxz5Vu-1681568892778)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_22_12.png)]
图 12:绘图输出
不同的剧集需要完成不同的步骤。 如果您滚动查看打印出来的信息,您将能够看到。 希望,当您运行本示例时,您至少在大多数情况下都会看到棘突。 凭借在本章中获得的知识,我认为我们还没有准备好在 Go 的游戏中击败 AlphaZero。 但是我们了解了如何构建此类系统的基础知识。
总结
在本章中,我们了解了 RL 系统。 我们讨论了 RL 的前提以及如何设置它。 我们讨论了 RL 和监督学习之间的区别。 我们浏览了一些真实的 RL 示例,并了解了各种系统如何以不同形式使用它。
我们讨论了 RL 的构建块以及诸如智能体,环境,策略,奖励等概念。 然后,我们在 Python 中创建了一个环境,以查看其运行情况。 最后,我们使用这些概念来构建 RL 智能体。
在下一章中,我们将进入一个截然不同的主题,并学习大数据技术如何帮助我们使机器学习系统更强大,更高效。
23 人工智能和大数据
在本章中,我们将学习什么是大数据以及如何在人工智能的背景下使用大数据技术。 我们将讨论大数据如何帮助加速机器学习流程。 我们还将讨论使用大数据技术什么时候是一个好主意,何时使用它们是过时的,并通过一些示例来加深我们的理解。 我们将学习使用大数据的机器学习管道的组成部分以及所涉及的各种挑战,并且将在 Python 中创建一个环境,以了解其在实践中的工作方式。 在本章的最后,我们将介绍:
- 大数据基础
- 大数据的三个 V
- 适用于人工智能和机器学习的大数据
- 使用大数据的机器学习管道
- Apache Hadoop
- Apache Spark
- Apache Impala
- NoSQL 数据库
让我们从大数据的基础开始。
大数据基础
是您今天定期进行的一项活动,而您十年前很少做,而且肯定二十年前从未做过。 但是,如果被告知您再也做不到,您会感到完全受阻。 您可能已经在今天或至少在本周进行了几次。 想猜一猜我在说什么吗? 我说的是执行 Google 搜索。
Google 已经存在了很短的时间,但是现在我们非常依赖它。 它扰乱并颠覆了包括杂志出版,电话簿,报纸等在内的众多行业。 如今,每当我们遇到知识难题时,我们都会用 Google 抓它。 由于我们通过手机永久链接到互联网,因此尤其如此。 Google 几乎已经成为我们的延伸。
但是,您是否已停止考虑所有这些令人难以置信的知识发现背后的机制? 实际上,我们触手可及的位置上有数十亿个文档,可以在几毫秒内访问它们,就像魔术一样,最相关的文档通常显示在前几个结果中,通常会为我们提供所需的答案。 有时我会觉得 Google 正在读我的想法,并且在开始键入关键字之前就知道我想要什么。
Google 提供这些答案的基本技术之一就是通常所说的大数据。 但是什么是大数据? 这个名字本身并没有给我们太多。 数据可以是存储在计算机上的任何数据,而大是相对术语。 在定义什么是大数据之前,让我先介绍一下我对该主题最喜欢的观察之一:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MohMW8x-1681568892778)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_23_01.png)]
图 1:大数据类比
这句话使人们明白,大数据对不同的人意味着不同的事物。 这种情况开始改变,但是传统数据库在我们可以存储多少信息以及可扩展的最大范围上有一个上限。 如果您正在使用 Hadoop,Spark,NoSQL 数据库和图数据库等新技术,那么您正在使用大数据。 没有什么是无限的,但是这些技术可以横向扩展(而不是纵向扩展),因此可以更有效地扩展规模。 没有任何东西可以无限扩展,但是在传统技术(如传统数据库)与新技术(如 Hadoop 和 Spark)之间存在根本差异。 随着供应商和诸如 Snowflake 之类的工具的出现,也开始使用普通的 SQL 处理 PB 级数据库。
随着业务的增长和对资源的需求的增加,如果他们使用的是传统架构,则他们将通过升级到更大,功能更强的计算机来扩展。 可以预见,这种增长方式将达到极限。 硬件制造商制造的机器只有这么大。
相反,如果企业正在使用新技术并且遇到瓶颈,则可以通过向外扩展并向其中添加新机器而不是更换硬件来突破瓶颈。 由于我们正在增加火力,因此这个新增加的不必一定更大。 它通常与旧机器相同。 这种增长方法更具可扩展性。 我们可能有成千上万的机器协同工作来解决问题。
由一组计算资源进行的协作通常称为集群。 集群是统一的节点的集合,它们在一个共同的目标中进行协作,例如存储和分析大量结构化或非结构化数据。 这些集群通常在低成本商品硬件上运行开源分布式处理软件。
这样的集群在低成本的商用计算机上运行 Hadoop 的开源分布式处理软件。 群集通常称为,非共享系统,因为节点之间唯一共享的资源是连接它们的网络。
集群架构可以大大提高数据分析的处理速度。 该架构还具有高度可扩展性:当集群的处理能力因某个工作负载而无法承受时,可以轻松添加其他节点以提高吞吐量。 此外,集群架构本质上是容错的。
每个数据组件都冗余地复制到多个节点上,以确保不会因为单个节点故障而丢失数据。 数据丢失并非不可能,但极不可能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LPP0bSfY-1681568892778)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_23_02.png)]
图 2:集群和节点
为了更好地理解大数据,让我们继续研究利用大数据技术的最佳示例之一。 让我们继续分析 Google 搜索。 出于显而易见的原因,Google 搜索的许多实际内部运作都是专有的。 我们正在推测,但很可能只有 Google 内部的一些员工才能知道 Google 工作方式的确切细节。
可能没有人完全了解或了解 Google 搜索的每个细节。 我们其他人会根据 Google 时不时发布的各种信息做出有根据的猜测。
推测它可能如何工作仍然是有益的(而且很有趣)。 Google 的界面使它看起来很简单,除了用一组单词接收请求并根据该请求返回响应外,似乎并没有做其他事情。 我仍然记得我第一次看到 Google 搜索页面,并且感到非常困惑,无法确定页面是否已完成加载。 那是在拨号调制解调器时代,雅虎比 Google 更大。 如果您最近访问过 Yahoo 主页并且看到它仍然很忙,您将理解我的困境。 但是,通过简单的 Google 搜索请求,幕后发生了很多事情。 更进一步,将采取以下步骤:
- 接收查询
- 查询解析
- 建立和规范字序
- 在 Google 索引中查找信息。 在执行此查找时,通过考虑以下因素来个性化结果:
- 个人喜好
- 以前的搜索记录
- Google 知道的关于您的个人信息(很多)
- 对结果进行排名(对结果进行排名的大部分工作已在发送查询之前完成)
- 发回响应
这是一个示例查询,找到了多少个结果以及返回结果所花费的时间:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BMc8zXpO-1681568892778)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_23_03.png)]
图 3:Google 搜索,显示的结果数
一个重要的考虑因素是,当您提交查询时,Google 不会搜索互联网。 到进行查询时,为快速响应已为您完成了许多工作。 在以下各节中,我们将讨论一些在后台进行的活动。
爬取
为了让 Google 提供结果,它需要知道可用的内容。 并没有神奇的中央存储库,它具有所有网页和网站,因此 Google 会不断搜索新的网站和页面,并将它们添加到其已知页面列表中。 此发现过程称为爬网。
有些页面在 Google 的网站列表中,因为 Google 以前已经对其进行了爬网。 当 Google 跟踪从已知页面到新页面的链接时,就会发现某些页面。 网站所有者或智能体提交网站以使 Google 知道该网站及其站点地图时,还会发现其他页面。
索引
一旦发现页面并将其添加到 Google 的网站列表中,Google 就会尝试了解其内容。 此过程称为索引编制。 Google 分析网站的内容,并对网站中的图像和视频进行分类。
然后将结果存储在 Google 索引中。 您可以想象,该索引确实符合大数据的定义。
通过创建此索引,Google 会搜索其互联网索引,而不是搜索互联网。 这似乎是一个很小的细节,但这是 Google 搜索如此快速的重要原因。
这样想吧。 当您去图书馆时,可以通过以下两种方式之一来查找书籍。 您可以转到卡片目录(假设它是一所旧学校图书馆),也可以在过道周围走动,直到找到所需的书。 您认为哪种方法会更快?
将典型库中包含的信息与 Google Universe 中包含的数据进行比较,我们很快就会意识到反对索引而不是原始数据的重要性。
Google 的工作方式与图书馆卡目录类似,不同之处在于 Google 的“目录”或索引包含指向大量网页的指针,并且比本地图书馆目录中的索引大得多。 根据 Google 自己的文档,他们承认其索引至少为 100 PB,并且可能是该索引的许多倍。 更多信息可以在这里找到。
如果您好奇的话,这里有一个很酷的网站,它将为您提供有关被索引的网页数量的实时估计。 试试看。
排名
当您使用搜索引擎寻找问题的答案时,Google 会使用许多因素来提供最相关的答案。 最重要的之一是 PageRank 的概念。 我们将让读者进一步了解该概念。 Google 使用其他标准来确定相关性,包括:
- 用户语言设置
- 以前的搜索记录
- 地理位置
- 设备(台式机或电话)的类型,品牌和型号
例如,搜索药房将为班加罗尔的用户提供不同于伦敦用户的结果。
正如我们在上一节中所看到的,Google 做了很多工作来使搜索快速进行。 但这并不是 Google 提供其魔力的全部。 Google 还可以如何加快搜索速度?
全球数据中心
与可口可乐配方一样,Google 喜欢将其秘方成分保密,因此 Google 不会发布有关其拥有多少个数据中心以及它们位于何处的详细信息。 可以肯定的是,他们有很多,而且遍布世界各地。
无论如何,当您向 Google 提交查询时,智能网络路由器都会将您的搜索查询扩展到距离您最近的且可用于执行搜索的数据中心。
更多信息可以在这里找到。
分布式查找
因此,Google 有许多数据中心,这些数据中心中的每一个都容纳数百个(如果不是数千个)商用服务器。 这些计算机已连接并以同步方式一起工作以执行用户请求。 当搜索查询到达数据中心时,主服务器接收到该请求,然后将其分解为较小的批量并将查找作业分配给多个从属节点。 这些从属节点中的每一个都分配有 Google Web 索引的分区,并分配了针对给定查询返回最相关结果的任务。 这些结果将返回到主服务器,然后由主服务器进一步过滤,组织和排序合并的结果,然后再将其发送回请求者。
当查询提交给 Google 时,它利用了其中一些服务器的功能。 这些服务器可以处理同时查找。
定制软件
支持这些服务器的绝大部分(即使不是全部)关键软件都是由 Google 工程师量身定制的,专门供 Google 使用,包括:
- 用于爬取网站的自定义蜘蛛
- 专有内部数据库
- 定制编程语言
- 称为 Google 文件系统(GFS)或 Colossus 的专有文件系统
希望您觉得自己对大数据有了更好的了解。 现在让我们增加一些混乱,让我们看看为什么当进入大数据时,大小并不是唯一重要的事情。
大数据的三个 V
不久以前习惯了在 90 天左右后清除应用生成的任何日志。 最近,公司意识到他们正在丢弃信息的金块。 此外,存储已经变得足够便宜,以至于保留这些日志都不费吹灰之力。 而且,云,互联网和技术的普遍发展现在可以创建更多的数据。 从智能手机到 IoT 小工具,工业传感器和监控摄像头,用于存储和传输数据的设备数量在世界范围内呈指数级增长,导致数据量激增。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nr95ZmYO-1681568892779)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_23_04.png)]
图 4:大数据的三个 V
根据 IBM 的数据,2012 年每天产生 2.5 EB 的数据。无论以何种方式衡量,这都是一个很大的数字。 同样,大约 75% 的数据是非结构化的,来自文本,语音和视频等来源。
这么多正在创建的新数据没有被构造的事实带来了关于大数据的另一个好处。 数据量不是使大数据难以处理的唯一特征。 在处理数据时,至少要考虑另外两个项目,这会增加处理的复杂性。 它们通常被称为速度和变化。 让我们仔细看看通常所说的三个 V。
容量
由于明显的原因,卷是最常与大数据关联的特性。 让我们来看一些其他示例,以及在这种情况下大数据的含义。
在其他服务中,Facebook 存储图像。 没关系吧? 但是,让我们回顾一下有关 Facebook 的一些统计数据:
- Facebook 的用户数量超过中国的用户数量
- 每天约有 50 万人创建新的个人资料
- Facebook 正在存储约 2500 亿张图像
那是大数据,那是数量。 对于 Facebook 和我们周围的整个世界,这些数字将继续增长。 考虑一下,当今世界上的手机数量已经超过了世界人数,并且每个人都在不断收集数据并将其发送到世界各地的 Google,Apple 和 Facebook。 大数据挑战将不断扩大。 堆积在现在变得流行的物联网和智能家居设备上,也许我们现在会开始不知所措。
速度
速度是衡量数据输入速度的指标。处理 TB 和 PB 的数据是一项挑战。 另一个更大的挑战是处理以下事实:该数据可能不是静态的,并且会持续增长。 在生成时,我们不仅必须接收它,而且在某些情况下,可能还需要对其进行处理,转换,分析和用于训练。
如果我们只需要担心数据量,就可以潜在地使用传统技术来处理数据并最终完成工作。 数据进入的速度要求以快速有效的方式处理数据,最重要的是,以快速的速度使用它来从中获取见解。
坚持使用 Facebook 示例。 2500 亿张图片肯定是一个大数目,而其他统计数据当然也可以视为大数目。 但这不是一个静态数字。
回想一下 2012 年的每一分钟:
- 发表了 510,000 条评论
- 更新了 293,000 个状态
- 上传了 136,000 张照片
很难理解这一卷的庞大性。 每天,这相当于大约:
- 每天 7.35 亿条评论
- 每天 4.21 亿状态更新
- 每天 1.95 亿张上传图片
如今,Facebook 对其指标的要求越来越严格,因此很难获得最新的统计数据。 有关 Facebook 流量和其他热门网站的更多有趣统计信息,请参见以下网址。
当涉及到数据时,Facebook 必须将其提取,处理,索引,存储,然后再进行检索。 这些数字会略有不同,具体取决于您相信的来源以及数据的最新程度。 Facebook 最近由于政治原因而成为新闻。 忘记周围的所有争论。 您能想象他们手头上存在的技术问题,根据我们上面描述的数量和速度来确定图像是否令人反感,有争议,政治,真实或不真实等等。
可以想象,这些数字一直在增加。 就像在物理学中一样,您不仅具有速度,还可能具有加速度。
种类
在过去的好日子中,大多数数据存储在整齐的列和行中。 随着我们收集越来越多的数据,情况已不再如此。 我们正在收集和存储照片,传感器数据,IoT 设备信息,推文,加密的数据包,语音,视频等。 这些都彼此非常不同。 这些数据不是过去几天的整齐的行和列以及数据库联接。 其中大部分是非结构化的。 这意味着它不容易存储在数据库中并建立索引。
为了更好地理解,让我们从一个简单的示例开始。 假设您是即将发生的重要案件的原告律师。 作为发现的一部分,辩护团队最近向您发送了一堆在线和物理文件(在法律背景下,发送是原告和辩方交换他们将在审判期间出示的证据的过程),这样他们每个人都可以提出反驳。 这些文件包括语音邮件,电话录音,电子邮件,录像内容,物理文件,电子邮件(带有附件)和计算机文件。
法律上要求辩护人出示与案件有关的所有文件,但不需要他们为您整理文件。 他们向您发送额外的信息以增加混乱的情况并不少见。 辩护人只希望您错过一些关键信息,而这些关键信息可能是破解您的案件并为您的客户取得积极成果的关键。 法庭肯定会在您需要陈述案件的时间上设定最后期限和里程碑。 即使您在数量和速度上都没有处理 Facebook 级别的数字,在这种情况下,您数据的多样性比大多数 Facebook 数据更为复杂。 这就是使您的工作困难的原因。
我们不会详细介绍如何解决该项目,但让我们重点介绍一些必须解决的任务,其中机器学习和大数据将帮助您解决:
- 扫描物理文件并执行 OCR。
- 转录语音邮件,电话录音和录像内容。 索引它们。 我们正在尝试将数据格式标准化为机器可读的计算机文件,然后对其进行统一扫描和搜索。
- 从电子邮件中分离附件。
- 编制索引并使所有信息可搜索(有开源和专有工具可以帮助此过程,例如 Solr,Sinequa,Elastic Search 和 Amazon Kendra)。
在本部分中,我们确定了,这些方法可以使用大数据工具和各种见识来筛查所有这些疯狂问题,从而使用机器学习来解决问题,辨别模式并确定机会。 在以下各节中,我们将学习可用于实现此目的的实际工具。
大数据与机器学习
大数据技术已被世界各地的技术公司成功利用。 当今的企业了解大数据的力量,他们意识到与机器学习结合使用时,其功能甚至会更加强大。
机器学习系统与大数据技术相结合,以多种方式帮助企业,包括比以往任何时候都更具战略性地管理,分析和使用捕获的数据。
随着公司捕获并生成不断增长的数据量,这既是挑战,也是巨大的机遇。 幸运的是,这两种技术是共生的。 企业不断提出新的模型,这些模型增加了最终工作负载的计算需求。 大数据方面的新进展使这些新用例的处理变得容易并得到促进。 数据科学家发现,当前的架构可以处理增加的工作量。 因此,他们提出了分析此数据的新方法,并从可用的当前数据中获得了越来越深刻的见解。
精心设计的算法可以在大型数据集上蓬勃发展,这意味着数据越多,学习越有效。 随着大数据的出现以及计算能力的提高,机器学习继续以加速的速度发展。 随着大数据分析在机器学习领域的稳定增长,机器和设备将变得越来越智能,并继续承担以前为人类保留的任务。
这些公司正在创建数据管道,然后为机器学习模型提供数据,以不断改善运营。 以亚马逊为例。 像 Google 一样,Amazon 可能对其某些统计数据保持沉默。 幸运的是,可以获得一些信息。 2017 年,亚马逊向全球运送了 50 亿件物品。 每天有 1300 万件商品,或者每秒至少有 150 件商品。 如果我们要使用传统的数据技术来处理大量数据并将机器学习模型应用于该数据,那么在我们能够进行有用的推断之前,我们将收到更多的交易。 通过使用大数据技术,Amazon 可以处理这些交易并使用机器学习从其运营中获得有用的见解。
一个具体的例子是 Amazon 复杂的欺诈检测算法。 抓获欺诈行为的案例非常重要。 理想情况下,他们希望在发生之前将其捕获。 考虑到其交易量,许多检查需要同时进行。 有趣的是,在某些情况下,Amazon 不会尝试最小化欺诈数量,而是选择最大化客户满意度和服务可用性。 例如,当人们使用预付卡时,AWS 服务中发生的许多欺诈行为都会发生。 一种最小化欺诈的简单解决方案是禁止使用预付卡,但亚马逊仍然接受这种付款方式,取而代之的是,即使用户选择使用此付款方式,亚马逊也敦促其数据科学家提出最小化欺诈的解决方案。
在欺诈这个话题上,有趣的是,亚马逊在其 2019 年 re:Invent 会议上宣布了一项名为 Fraud Detector 的新服务。 它使用了与 Amazon 用来捕获欺诈的相同技术,并允许该服务的用户在自己的操作和交易中防止欺诈。 有关服务的更多信息,可以在这里找到。
在前面的部分中,我们了解了大数据技术本身是如何强大的。 本节的重点是要理解机器学习是大数据集群可以成功进行并以大规模并行方式处理的工作负载之一。 许多大数据栈(例如 Hadoop 栈)具有内置的机器学习组件(例如 Mahout),但是我们不限于仅使用这些组件来训练机器学习模型。 这些栈可以与其他同类最佳的 ML 库结合使用,例如 Scikit-Learn,Theano,Torch,Caffe 和 TensorFlow。 现在我们已经讨论了大数据如何帮助我们创建机器学习模型,让我们进一步了解一些当今最流行的大数据工具。
Apache Hadoop
Hadoop 是 Apache Software Foundation 旗下的一种流行的开源框架,它促进了多台计算机的联网,以处理,转换和分析大型数据集。 我们将在下一节中进一步讨论。 随着名为 Apache Spark 的新 Hadoop 组件的出现,还有其他处理该数据的方法,但是最初,许多工作负载是通过使用 MapReduce 编程范例来处理的。
Hadoop 框架被许多财富 500 强公司使用,包括 Facebook,Netflix 和其他公司。
Hadoop 可以与各种其他技术解决方案和第三方软件(包括机器学习工具)集成。
Hadoop 无疑是构建可伸缩大数据解决方案的最佳开源工具。 它可以处理海量的分布式数据集,并且可以轻松地与其他技术和框架集成。 让我们了解 Hadoop 生态系统中的一些核心概念。
MapReduce
Hadoop 核心高度依赖 MapReduce。 MapReduce 是一种编程设计模式,用于使用并行,分布式集群计算来处理,转换和生成大型数据集。 MapReduce 模型通过协调分布式资源,并行运行多个作业以及同步节点之间的通信和数据传输来完成大规模数据集的处理。 通过跨节点复制数据,它可以很好地处理各个节点何时出现故障。
MapReduce 仅在多节点群集架构模型的上下文中才有意义。 MapReduce 背后的想法是,许多问题可以分为两个步骤。 映射和归约。
MapReduce 实现由一个映射组件和一个归约组件组成,映射组件执行映射,过滤和/或排序(例如,将不同家用物品的图像映射到队列中,每种类型的物品一个队列),归约组件完成摘要操作(继续示例,计算每个队列中的图像数量,为我们提供家用物品的频率)。
Apache Hive
有许多组件组成了 Hadoop 生态系统,并且有涵盖该主题的完整书籍。 例如,我们推荐 Manish Kumar 和 Chanchal Singh 的《Mastering Hadoop 3》。 其中许多组件不在本书讨论范围之内,但是在结束本主题之前,我们应该介绍的 Hadoop 的一个重要组件是 Apache Hive。
Apache Hive 是构建在 Apache Hadoop 之上的数据仓库软件组件,支持数据查询和分析。 Apache Hive 支持类似 SQL 的界面,以获取 Hadoop 支持的各种数据库和文件系统中存储的数据。 如果没有 Hive,则需要实现复杂的 Java 代码以提供必要的 MapReduce 逻辑。 Apache Hive 提供了一种抽象,以支持对底层 Java 的类似 SQL 的查询(HiveQL),而无需实现复杂的低级 Java 代码。
由于大多数数据仓库应用都支持基于 SQL 的查询语言,因此 Hive 促进并支持将基于 SQL 的应用移植到 Hadoop 中。 Hive 最初是由 Facebook 开发的,但现在已集成到 Hadoop 生态系统中,并被众多财富 500 强公司使用。
Apache Spark
Apache Spark 是,是另一个流行的开源框架,隶属于 Apache Software Foundation。 对于许多用例,可以使用 Spark 代替 Hadoop 来解决相同的问题。
Hadoop 是最早开发的,因此,它有很多心得和实现。 但是在许多情况下,Spark 可能是更好的选择。 这样做的主要原因是 Hadoop 通过将文件读写到磁盘上的 HDFS 来处理其大部分处理,而 Spark 使用称为 RDD 或弹性分布式数据集的概念在 RAM 中处理数据。
弹性分布式数据集
弹性分布式数据集(RDD)是 Spark 的基础组件。 RDD 具有以下特征:
- 不变性
- 分布式
- 始终驻留在内存中,而不驻留在磁盘存储中
RDD 中的数据集被划分为逻辑分区,这些逻辑分区冗余地存储在集群中的各个节点上。 这些对象可以是用户定义的类。 最初,只能使用 Scala 语言创建 RDD,但现在也支持 Java 和 Python。
更正式地说,RDD 是不可变的分区记录集。 RDD 是通过一系列确定性操作生成的。 RDD 是一组弹性的内存对象,可以并行且大规模地进行操作。
可以使用以下两种方法之一来创建 RDD::
- 通过并行化驱动程序中的现有集合
- 通过引用来自外部磁盘存储的数据集,例如共享文件系统,S3,HBase,HDFS 或任何其他支持 Hadoop 兼容输入格式的外部数据源
Spark 利用 RDD 的概念来实现更快,更有效的 MapReduce 操作。 使用第二版 Spark,现在还支持更简单的数据结构,从而简化了数据集的处理。 这些是数据帧。
数据帧
Spark 中的新抽象是数据帧。 最初通过引入 Spark 2.0 作为 RDD 的替代接口来支持数据帧。 这两个接口有些相似。数据帧将数据组织到命名列中。 从概念上讲,它等效于关系数据库中的表或 Python 的 pandas 包或 R 中的数据帧。这使数据帧比 RDD 易于使用。 RDD 不支持类似的列级标题引用集。
数据帧可以从多种数据源生成,包括:
- Hive 中的结构化数据文件(例如,Parquet,ORC,JSON)
- Hive 表
- 外部数据库(通过 JDBC)
- 现有的 RDD
DataFrame API 在 Scala,Java,Python 和 R 中可用。
SparkSQL
SparkSQL 是,它可以激发出 Hive 对 Hadoop 的作用。 它允许 Spark 框架的用户查询数据框架,就像在传统的关系数据库中查询 SQL 表一样。
SparkSQL 添加了一个抽象级别,以允许查询存储在 RDD,数据帧和外部源中的数据,从而为所有这些数据源创建统一的接口。 提供这种统一的接口使开发人员可以轻松地创建复杂的 SQL 查询,以与异类的各种源(例如 HDFS 文件,S3,传统数据库等)相对。
更具体地说,SparkSQL 使用户能够:
- 对导入的数据和现有的 RDD 运行 SQL 查询
- 从 Apache Parquet 文件,ORC 文件和 Hive 表导入数据
- 将 RDD 和数据帧输出到 Hive 表或 Apache Parquet 文件
SparkSQL 具有列式存储,基于成本的优化器和代码生成以加快查询速度。 它可以使用 Spark 引擎支持数千个节点。
SparkSQL 无需修改即可运行 Hive 查询。 它重写了 Hive 前端和元存储,从而与现有 Hive 数据完全兼容。
Apache Impala
在某些方面,例如 Apache Spark,Apache Impala 是用于存储在运行的集群中的数据的开源大规模并行处理(MPP)SQL 引擎。 在 Apache Hadoop 上。 它在 Apache Hadoop 之上运行。
Impala 为 Hadoop 提供了可扩展的并行 SQL 数据库引擎,使开发人员可以对存储在 Apache HBase,Amazon S3 和 HDFS 中的数据创建并运行低延迟 SQL 查询,而无需在读取数据之前先进行移动或转换。
Impala 可以轻松与 Hadoop 集成,并支持 MapReduce,Apache Hive 和 Apache Pig 以及 Hadoop 栈中的其他工具所使用的相同文件和数据格式,元数据以及安全和资源管理框架。
分析人员和数据科学家都使用 Impala。 人们通过 SQL,BI 工具以及机器学习库,包和工具对 Hadoop 中存储的数据进行分析。 结果是大规模数据处理(通过 MapReduce)。
可以使用相同的数据和元数据在同一系统上启动查询,而不必将数据集迁移到专用系统中,也不必转换为专有文件格式即可执行简单的分析。
支持的功能包括:
- HDFS 和 Apache HBase 是受支持的格式
- 读取 Hadoop 文件格式,例如:
- 文本(CSV,JSON,XML)
- Parquet
- 序列文件
- Avro
- LZO
- RCFile
- 支持 Hadoop 安全性(例如 Kerberos 身份验证)
- 使用 Apache Sentry 的细粒度,基于角色的授权
- 使用 Apache Hive 中的元数据,ODBC 驱动程序和 SQL 语法
有关 Impala 及其历史的更多信息,可以在 Impala 文档中找到,以及这里。
现在,让我们分析另一个可以大大增强大型数据集处理能力的重要技术。 现在,我们将尝试了解什么是 NoSQL 数据库。
NoSQL 数据库
在深入研究 NoSQL 数据库的特定类型之前,我们首先了解什么是 NoSQL 数据库。 这不是一个好名字,但是很难提出更好的选择。 顾名思义,NoSQL 数据库是不是 SQL 数据库的任何数据库。 它包含各种数据库技术,这些技术必须针对能够应对更大工作量和更大,更多样化数据集的产品的市场需求而构建。
数据是新的石油,它存在于各种各样的地方。 日志文件,音频,视频,点击流,IoT 数据和电子邮件是需要处理和分析的数据的一些示例。 传统的 SQL 数据库在使用数据之前需要结构化的架构。 此外,它们的构建并不是为了利用当今容易获得的商品存储和处理能力。
NoSQL 数据库的类型
文档数据库:文档数据库用于存储半结构化数据。 他们使用与复杂的数据结构(称为文档)配对的键。 文档可以包含许多类型的数据结构,例如原始值(如整数,布尔值和字符串),不同的键值对或键数组对,甚至是嵌套文档。
图数据库:图数据库将图结构用于具有节点,边和属性的语义查询,以表示和存储数据。 图数据库的重点是数据中的关系。 一些示例使用图数据库的案例:
- Facebook 中包含的信息以及网络中朋友之间的关系。
- 交易,客户和银行帐户。 即,使用帐户 X 的客户 A 向具有帐户 Y 的客户 B 汇款。
- 一个家庭的祖先信息。 该案例中的关系示例包括配偶,兄弟姐妹,父母,孩子,叔叔等。
图数据库的示例是:
- Neo4J
- Giraph
- Tiger Graph
- 亚马逊 Neptune
- Azure Cosmos
键值数据库:键值数据库是 NoSQL 数据库的最简单类型。 使用属性名称(或键)及其值存储数据库中的每个单项。 键值存储的一些示例是:
- Riak
- RocksDB
- Apache Ignite
- Berkeley DB
- ArangoDB
- Redis
按列数据库:按列数据库已针对大型数据集的查询进行了优化,并将数据列而不是行存储在一起。 这些类型的数据库的示例是 Cassandra 和 HBase。
现在,让我们进一步详细介绍 NoSQL 数据库的一些最流行的实现。
Apache Cassandra
Apache Cassandra 是,一个开放源代码的分布式 NoSQL 数据库。 它可以处理大量数据。 我利用了可以使用许多商品服务器进行处理的水平架构。 它具有无单点故障的架构,因此可提供高可用性。 Cassandra 依赖可以跨越多个数据中心和区域的集群。 它使用异步无主复制,同时为用户提供低延迟的操作。
Avinash Lakshman(亚马逊 DynamoDB 的作者之一)和 Prashant Malik 最初在 Facebook 上开发了 Cassandra。 Facebook 将 Cassandra 作为开源项目发布,并在 2010 年成为 Apache 的顶级项目。
MongoDB
MongoDB 是面向文档的水平可伸缩数据库。 它使用 JSON 数据格式存储数据。 它通常用于存储网站数据,并且在内容管理和缓存应用中也很流行。 它支持复制和高可用性配置,以最大程度地减少数据丢失。
根据查询的类型,它可能是一个高性能的系统。 它是用 C 编写的。 它完全支持索引,具有丰富的查询语言,可以进行配置以提供跨数据中心的高可用性。
Redis
Redis 是的另一个开源 NoSQL 数据库。 这是一个键值存储。 它支持键中的哈希,集,字符串,排序集和列表。 因此,Redis 也被称为数据结构服务器。 Redis 支持运行原子操作,例如增加散列中存在的值,设置交集计算,字符串附加,差和联合。 Redis 利用内存中的数据集来实现高性能。 Redis 支持大多数最受欢迎的编程语言,例如 Python,Java,Scala,C 等。
Neo4j
Neo4j 是 Neo4j,Inc.开发的图数据库管理系统。它最初于 2010 年 2 月发布。它在本机图存储和处理中支持符合 ACID 的事务。 Neo4j 可能是最受欢迎的图数据库。
Neo4j 具有开源版本和付费版本。 Neo4j 的开源版本可通过 GPL3 许可的开源“社区版”获得,而通过开源商业许可证,您可以拥有在线备份和高可用性扩展。 Neo 还根据开源商业条款向这些扩展提供了 Neo4j 许可。
Neo4j 用 Java 实现,并通过交易 HTTP 端点或二进制“螺栓”协议使用 Cypher Query Language 支持最受欢迎的语言。
还有许多其他流行的数据库实现。 我们几乎没有吃过那个宇宙的表面。 我们衷心希望您能为本书中介绍的概念更好地应对下一个数据科学项目。
总结
在本章中,我们首先围绕大数据奠定了核心和基本概念的基础。 然后,我们了解了与大数据有关的许多不同技术。 当涉及到大数据技术 Hadoop 时,我们了解了其中的所有“老祖宗”。 我们还了解了当今市场上当前最流行的大数据工具,即 Spark。
最后,我们了解了大数据实现中常用的另一种技术,即 NoSQL 数据库。 NoSQL 数据库引擎支持财富 500 强公司中许多最大的工作量,并在当今最常见的网站中服务数百万个页面。
对于当今机器学习中存在的所有令人惊奇和令人兴奋的应用,我们坚信,我们只是在探索一切可能的事物。 我们衷心希望您能更好地掌握机器学习中涉及的概念,但更重要的是,我们希望您的好奇心得到激发,这本书激发您毕生对这种美丽话题的兴趣。
我们很高兴看到您使用本章,本书中所包含的知识做什么。 希望您发现这本书引人入胜,有趣并且有用。 我们希望您阅读和阅读时一样开心。 我们希望您在所有努力中继续取得成功。