每个程序员都应该知道的 40 个算法(三)

原文:zh.annas-archive.org/md5/8ddea683d78e7bd756401ec665273969

译者:飞龙

协议:CC BY-NC-SA 4.0

第九章:自然语言处理算法

本章介绍了自然语言处理NLP)的算法。本章从理论到实践逐步进行。它将首先介绍 NLP 的基础知识,然后介绍基本算法。然后,它将研究最流行的神经网络之一,该网络被广泛用于设计和实施文本数据的重要用例的解决方案。最后,我们将研究 NLP 的局限性,最后学习如何使用 NLP 来训练一个可以预测电影评论极性的机器学习模型。

本章将包括以下部分:

  • 介绍 NLP

  • 基于词袋(BoW)的 NLP

  • 词嵌入介绍

  • 使用递归神经网络进行 NLP

  • 使用 NLP 进行情感分析

  • 案例研究:电影评论情感分析

通过本章结束时,您将了解用于 NLP 的基本技术。您应该能够理解 NLP 如何用于解决一些有趣的现实世界问题。

让我们从基本概念开始。

介绍 NLP

NLP 用于研究形式化和规范化计算机与人类(自然)语言之间的交互。NLP 是一个综合性的学科,涉及使用计算机语言学算法和人机交互技术和方法来处理复杂的非结构化数据。NLP 可以用于各种情况,包括以下情况:

  • 主题识别:发现文本存储库中的主题,并根据发现的主题对存储库中的文档进行分类

  • 情感分析:根据文本中包含的积极或消极情感对文本进行分类

  • 机器翻译:将文本从一种口头人类语言翻译成另一种口头人类语言

  • 文本转语音:将口头语言转换为文本

  • 主观解释:智能地解释问题并利用可用信息回答问题

  • 实体识别:从文本中识别实体(如人、地点或物品)

  • 假新闻检测:根据内容标记假新闻

让我们首先看一些在讨论 NLP 时使用的术语。

理解 NLP 术语

NLP 是一个综合性的学科。在围绕某一领域的文献中,我们会观察到,有时会使用不同的术语来指定相同的事物。我们将从一些与 NLP 相关的基本术语开始。让我们从规范化开始,这是一种基本的 NLP 处理,通常在输入数据上执行。

规范化

规范化是对输入文本数据进行的处理,以提高其在训练机器学习模型的情况下的质量。规范化通常包括以下处理步骤:

  • 将所有文本转换为大写或小写

  • 去除标点符号

  • 去除数字

请注意,尽管通常需要前面的处理步骤,但实际的处理步骤取决于我们想要解决的问题。它们会因用例而异,例如,如果文本中的数字代表了在我们尝试解决的问题的情境中可能具有一些价值的东西,那么我们在规范化阶段可能就不需要从文本中去除数字。

语料库

我们用来解决问题的输入文档组称为语料库。语料库充当 NLP 问题的输入数据。

标记化

当我们使用 NLP 时,第一项工作是将文本分成一个标记列表。这个过程称为标记化。由于目标的不同,生成的标记的粒度也会有所不同,例如,每个标记可以包括以下内容:

  • 一个词

  • 一组单词的组合

  • 一个句子

  • 一个段落

命名实体识别

在 NLP 中,有许多用例需要从非结构化数据中识别特定的单词和数字,这些单词和数字属于预定义的类别,如电话号码、邮政编码、姓名、地点或国家。这用于为非结构化数据提供结构。这个过程称为命名实体识别NER)。

停用词

在单词级别的标记化之后,我们得到了文本中使用的单词列表。其中一些单词是常见单词,预计几乎会出现在每个文档中。这些单词不会为它们出现在的文档提供任何额外的见解。这些单词被称为停用词。它们通常在数据处理阶段被移除。一些停用词的例子是waswethe

情感分析

情感分析,或者称为意见挖掘,是从文本中提取正面或负面情感的过程。

词干提取和词形还原

在文本数据中,大多数单词可能以稍微不同的形式存在。将每个单词减少到其原始形式或词干所属的词族中称为词干提取。它用于根据它们的相似含义对单词进行分组,以减少需要分析的单词总数。基本上,词干提取减少了问题的整体条件性。

例如,{use, used, using, uses} => use。

英语词干提取的最常见算法是波特算法。

词干提取是一个粗糙的过程,可能会导致词尾被截断。这可能导致拼写错误的单词。对于许多用例来说,每个单词只是我们问题空间中的一个级别的标识符,拼写错误的单词并不重要。如果需要正确拼写的单词,那么应该使用词形还原而不是词干提取。

算法缺乏常识。对于人类大脑来说,将类似的单词视为相同是很简单的。对于算法,我们必须引导它并提供分组标准。

从根本上讲,有三种不同的 NLP 实现方法。这三种技术在复杂性方面有所不同,如下所示:

  • 基于词袋模型(BoW-based)的 NLP

  • 传统的 NLP 分类器

  • 使用深度学习进行自然语言处理

NLTK

自然语言工具包NLTK)是 Python 中处理 NLP 任务最广泛使用的包。NLTK 是用于 NLP 的最古老和最流行的 Python 库之一。NLTK 非常好,因为它基本上为构建任何 NLP 流程提供了一个起点,它为您提供了基本工具,然后您可以将它们链接在一起以实现您的目标,而不是从头开始构建所有这些工具。许多工具都打包到了 NLTK 中,在下一节中,我们将下载该包并探索其中的一些工具。

让我们来看看基于词袋模型的 NLP。

基于词袋模型的 NLP

将输入文本表示为一组标记的过程称为基于词袋模型的处理。使用词袋模型的缺点是我们丢弃了大部分语法和标记化,这有时会导致丢失单词的上下文。在词袋模型的方法中,我们首先量化要分析的每个文档中每个单词的重要性。

从根本上讲,有三种不同的方法来量化每个文档中单词的重要性:

  • 二进制:如果单词出现在文本中,则特征的值为 1,否则为 0。

  • 计数:特征将以单词在文本中出现的次数作为其值,否则为 0。

  • 词项频率/逆文档频率:特征的值将是单个文档中单词的独特程度与整个文档语料库中单词的独特程度的比率。显然,对于常见单词,如 the、in 等(称为停用词),词项频率-逆文档频率TF-IDF)得分将很低。对于更独特的单词,例如领域特定术语,得分将更高。

请注意,通过使用词袋模型,我们丢失了信息——即文本中单词的顺序。这通常有效,但可能会导致准确性降低。

让我们看一个具体的例子。我们将训练一个模型,可以将餐厅的评论分类为负面或正面。输入文件是一个结构化文件,其中评论将被分类为正面或负面。

为此,让我们首先处理输入数据。

处理步骤在下图中定义:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/70cf5585-8206-45a8-9b86-8aa31f914b37.png

让我们通过以下步骤实现这个处理流程:

  1. 首先,让我们导入我们需要的包:
import numpy as np
import pandas as pd
  1. 然后我们从CSV文件中导入数据集:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/99fd1e80-976c-43b6-bf61-3e878651d157.png

  1. 接下来,我们清理数据:
# Cleaning the texts
import re
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
from nltk.stem.porter import PorterStemmer
corpus = []
for i in range(0, 1000):
    review = re.sub('[^a-zA-Z]', ' ', dataset['Review'][i])
    review = review.lower()
    review = review.split()
    ps = PorterStemmer()
    review = [ps.stem(word) for word in review if not word in set(stopwords.words('english'))]
    review = ' '.join(review)
    corpus.append(review)
  1. 现在让我们定义特征(用y表示)和标签(用X表示):
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(max_features = 1500)
X = cv.fit_transform(corpus).toarray()
y = dataset.iloc[:, 1].values
  1. 让我们将数据分成测试数据和训练数据:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20, random_state = 0)
  1. 对于训练模型,我们使用朴素贝叶斯算法:
from sklearn.naive_bayes import GaussianNB
classifier = GaussianNB()
classifier.fit(X_train, y_train)
  1. 让我们预测测试集的结果:
y_pred = classifier.predict(X_test)
  1. 混淆矩阵如下所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/d3c55c12-09ac-460b-a3c2-915340b89103.png

通过观察混淆矩阵,我们可以估计误分类情况。

词嵌入简介

在前面的部分中,我们学习了如何使用词袋模型作为输入文本数据的抽象来执行 NLP。NLP 的一个主要进展是我们能够以密集向量的形式创建单词的有意义的数值表示能力。这种技术称为词嵌入。Yoshua Bengio 首次在他的论文《神经概率语言模型》中引入了这个术语。NLP 问题中的每个词都可以被视为一个分类对象。将每个词映射到表示为向量的数字列表称为词嵌入。换句话说,用于将单词转换为实数的方法称为词嵌入。嵌入的一个区别特征是它使用密集向量,而不是使用传统方法使用稀疏矩阵向量。

使用词袋模型进行 NLP 存在两个基本问题:

  • 语义上下文的丢失:当我们对数据进行标记化时,它的上下文就丢失了。一个词可能根据它在句子中的使用位置有不同的含义;当解释复杂的人类表达时,比如幽默或讽刺,这变得更加重要。

  • 稀疏输入:当我们进行标记化时,每个单词都成为一个特征。正如我们在前面的例子中看到的,每个单词都是一个特征。这导致了稀疏的数据结构。

一个词的邻域

如何向算法呈现文本数据(特别是单词或词元)的关键见解来自语言学。在词嵌入中,我们关注每个词的邻域,并用它来确定其含义和重要性。一个词的邻域是围绕特定词的一组词。一个词的上下文是由它的邻域决定的。

请注意,在词袋模型中,一个词失去了它的上下文,因为它的上下文来自它所在的邻域。

词嵌入的特性

良好的词嵌入具有以下四个特性:

  • 它们是密集的:实际上,嵌入本质上是因子模型。因此,嵌入向量的每个组件代表一个(潜在)特征的数量。通常我们不知道该特征代表什么;但是,我们将有非常少的(如果有的话)零值,这将导致稀疏输入。

  • 它们是低维的:嵌入具有预定义的维度(作为超参数选择)。我们之前看到,在 BoW 表示中,我们需要为每个单词输入|V|,因此输入的总大小为|V| * n,其中n是我们用作输入的单词数。使用单词嵌入,我们的输入大小将是d * n,其中d通常在 50 到 300 之间。考虑到大型文本语料库通常远大于 300 个单词,这意味着我们在输入大小上有很大的节省,我们看到这可能导致更小的数据实例总数的更高准确性。

  • 它们嵌入领域语义:这个属性可能是最令人惊讶的,但也是最有用的。当正确训练时,嵌入会学习关于其领域的含义。

  • 易于泛化:最后,网络嵌入能够捕捉到一般的抽象模式——例如,我们可以对(嵌入的)猫、鹿、狗等进行训练,模型将理解我们指的是动物。请注意,模型从未接受过对羊的训练,但模型仍然会正确分类它。通过使用嵌入,我们可以期望得到正确的答案。

现在让我们探讨一下,我们如何使用 RNN 进行自然语言处理。

使用 RNN 进行 NLP

RNN 是一个具有反馈的传统前馈网络。对 RNN 的一种简单思考方式是,它是一个带有状态的神经网络。RNN 可用于任何类型的数据,用于生成和预测各种数据序列。训练 RNN 模型是关于构建这些数据序列。RNN 可用于文本数据,因为句子只是单词序列。当我们将 RNN 用于 NLP 时,我们可以用它来进行以下操作:

  • 在输入时预测下一个单词

  • 生成新的文本,遵循文本中已经使用的风格:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/8aba997b-e588-4c5d-8307-d33b6ad5c15b.png

还记得导致它们正确预测的单词组合吗?RNN 的学习过程是基于语料库中的文本。它们通过减少预测的下一个单词和实际的下一个单词之间的错误来进行训练。

使用 NLP 进行情感分析

本节介绍的方法是基于对分类高速流推文的使用情况。手头的任务是提取关于所选主题的推文中嵌入的情绪。情感分类实时量化每条推文中的极性,然后聚合所有推文的总情感,以捕捉关于所选主题的整体情感。为了应对 Twitter 流数据的内容和行为带来的挑战,并有效地执行实时分析,我们使用 NLP 使用训练过的分类器。然后将训练过的分类器插入 Twitter 流中,以确定每条推文的极性(积极、消极或中性),然后聚合并确定关于某一主题的所有推文的整体极性。让我们一步一步地看看这是如何完成的。

首先,我们必须训练分类器。为了训练分类器,我们需要一个已经准备好的数据集,其中包含有历史的 Twitter 数据,并且遵循实时数据的模式和趋势。因此,我们使用了来自网站www.sentiment140.com的数据集,该数据集带有一个人工标记的语料库(基于该分析的大量文本集合),其中包含超过 160 万条推文。该数据集中的推文已经被标记为三种极性之一:零表示负面,两表示中性,四表示正面。除了推文文本之外,语料库还提供了推文 ID、日期、标志和推文用户。现在让我们看看在训练分类器之前对实时推文执行的每个操作:

  1. 首先将推文分割成称为标记的单词(标记化)。

  2. 标记化的输出创建了一个 BoW,其中包含文本中的单个单词。

  3. 这些推文进一步通过去除数字、标点和停用词(停用词去除)进行过滤。停用词是非常常见的词,如isamarethe。由于它们没有额外的信息,这些词被移除。

  4. 此外,非字母字符,如*#**@*和数字,使用模式匹配进行删除,因为它们在情感分析的情况下没有相关性。正则表达式用于仅匹配字母字符,其余字符将被忽略。这有助于减少 Twitter 流的混乱。

  5. 先前阶段的结果被用于词干处理阶段。在这个阶段,派生词被减少到它们的词根-例如,像fish这样的词与fishingfishes具有相同的词根。为此,我们使用标准 NLP 库,它提供各种算法,如 Porter 词干处理。

  6. 一旦数据被处理,它被转换成一个称为术语文档矩阵TDM)的结构。TDM 表示过滤后语料库中每个词的术语和频率。

  7. 从 TDM 中,推文到达训练过的分类器(因为它经过训练,可以处理推文),它计算每个词的情感极性重要性SPI),这是一个从-5 到+5 的数字。正负号指定了该特定词所代表的情绪类型,其大小表示情感的强度。这意味着推文可以被分类为正面或负面(参考下图)。一旦我们计算了个别推文的极性,我们将它们的总体 SPI 相加,以找到来源的聚合情感-例如,总体极性大于一表示我们观察时间内推文的聚合情感是积极的。

为了获取实时原始推文,我们使用 Scala 库Twitter4J,这是一个提供实时 Twitter 流 API 包的 Java 库。该 API 要求用户在 Twitter 上注册开发者帐户并填写一些认证参数。该 API 允许您获取随机推文或使用选择的关键词过滤推文。我们使用过滤器来检索与我们选择的关键词相关的推文。

总体架构如下图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/3dc16321-97c8-4e5e-b815-3584e3e61f97.png

情感分析有各种应用。它可以用来分类客户的反馈。政府可以利用社交媒体极性分析来找到他们政策的有效性。它还可以量化各种广告活动的成功。

在接下来的部分,我们将学习如何实际应用情感分析来预测电影评论的情感。

案例研究:电影评论情感分析

让我们使用 NLP 进行电影评论情感分析。为此,我们将使用一些开放的电影评论数据,可在www.cs.cornell.edu/people/pabo/movie-review-data/上找到:

  1. 首先,我们将导入包含电影评论的数据集:
import numpy as np
import pandas as pd
  1. 现在,让我们加载电影数据并打印前几行以观察其结构。
df=pd.read_csv("moviereviews.tsv",sep='\t')
df.head()

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/d613c48f-67c9-44c0-97b0-db32f368deae.png

请注意数据集有2000条电影评论。其中一半是负面的,一半是正面的。

  1. 现在,让我们开始准备数据集以训练模型。首先,让我们删除数据中的任何缺失值
df.dropna(inplace=True)
  1. 现在我们需要移除空格。空格不是空的,但需要被移除。为此,我们需要遍历输入DataFrame中的每一行。我们将使用.itertuples()来访问每个字段:
blanks=[] 

for i,lb,rv in df.itertuples():
    if rv.isspace():
        blanks.append(i)
df.drop(blanks,inplace=True)      

请注意,我们已经使用ilbrv来索引、标签和评论列。

让我们将数据分割成测试和训练数据集:

  1. 第一步是指定特征和标签,然后将数据分割成训练集和测试集:
from sklearn.model_selection import train_test_split

X = df['review']
y = df['label']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

现在我们有测试和训练数据集。

  1. 现在让我们将数据集分成训练集和测试集:
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB

# Naïve Bayes:
text_clf_nb = Pipeline([('tfidf', TfidfVectorizer()),
                     ('clf', MultinomialNB()),
])

请注意,我们正在使用tfidf来量化集合中数据点的重要性。

接下来,让我们使用朴素贝叶斯算法来训练模型,然后测试训练好的模型。

让我们按照以下步骤来训练模型:

  1. 现在让我们使用我们创建的测试和训练数据集来训练模型:
text_clf_nb.fit(X_train, y_train)
  1. 让我们运行预测并分析结果:
# Form a prediction set
predictions = text_clf_nb.predict(X_test)

让我们通过打印混淆矩阵来查看模型的性能。我们还将查看精确度召回率F1 分数准确度

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/774aa10e-3479-403d-a3a5-949c7179ab32.png

这些性能指标为我们提供了预测质量的度量。准确率为 0.78,现在我们已经成功训练了一个可以预测特定电影评论类型的模型。

摘要

在本章中,我们讨论了与自然语言处理相关的算法。首先,我们研究了与自然语言处理相关的术语。接下来,我们研究了实施自然语言处理策略的 BoW 方法。然后,我们研究了词嵌入的概念以及在自然语言处理中使用神经网络。最后,我们看了一个实际的例子,我们在这一章中使用了开发的概念来根据电影评论的文本来预测情感。通过学习本章内容,用户应该能够将自然语言处理用于文本分类和情感分析。

在下一章中,我们将研究推荐引擎。我们将研究不同类型的推荐引擎以及它们如何用于解决一些现实世界的问题。

第十章:推荐引擎

*https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/b4fc1c9d-6a64-4b2a-853a-d10b5a53826a.png*推荐引擎是利用用户偏好和产品详情提供明智建议的一种方式。推荐引擎的目标是了解一组项目之间的相似性模式和/或制定用户和项目之间的互动。

本章首先介绍了推荐引擎的基础知识。然后,讨论了各种类型的推荐引擎。接下来,本章讨论了推荐引擎如何用于向不同用户建议项目和产品以及推荐引擎的各种限制。最后,我们将学习如何使用推荐引擎解决现实问题。

本章讨论了以下概念:

  • 推荐系统的介绍

  • 推荐引擎的类型

  • 了解推荐系统的限制

  • 实际应用领域

  • 一个实际的例子——创建一个推荐引擎向订阅者推荐电影

在本章结束时,您应该能够理解如何使用推荐引擎根据一些偏好标准建议各种项目。

让我们从推荐引擎的背景概念开始。

推荐系统的介绍

推荐系统代表研究人员最初开发的方法,用于预测用户最有可能感兴趣的项目。推荐系统在给出关于项目的个性化建议方面的能力使其可能是在线购买世界中最重要的技术。

在电子商务应用中,推荐引擎使用复杂的算法来改善购物体验,允许服务提供商根据用户的偏好定制产品。

2009 年,Netflix 向任何能够通过超过 10%改进其现有推荐引擎(Cinematch)的算法提供 100 万美元的奖金。奖金被 BellKor 的 Pragmatic Chaos 团队赢得。

推荐引擎的类型

通常有三种不同类型的推荐引擎:

  • 基于内容的推荐引擎

  • 协同过滤引擎

  • 混合推荐引擎

基于内容的推荐引擎

基于内容的推荐引擎的基本思想是建议与用户先前表现出兴趣的项目相似的项目。基于内容的推荐引擎的有效性取决于我们量化项目与其他项目的相似性的能力。

让我们看看下面的图表。如果用户 1已阅读文档 1,那么我们可以向用户推荐与文档 1相似的文档 2

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/085cbec3-1d5a-4bc1-9b87-52474dfa09c4.png

现在的问题是如何确定哪些项目彼此相似。让我们看看找到不同项目之间相似性的几种方法。

查找非结构化文档之间的相似性

确定不同文档之间相似性的一种方法是首先处理输入文档。处理非结构化文档后得到的数据结构称为术语文档矩阵TDM),如下图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/e9f0d053-8d92-424c-8fec-1bea05859e38.png

TDM 具有所有术语的词汇表作为行,所有文档作为列。它可以用于根据所选的距离度量确定哪些文档与其他文档相似。例如,Google 新闻根据与用户已经表现出兴趣的新闻相似性向用户推荐新闻。

一旦我们有了 TDM,有两种方法可以量化文档之间的相似性:

  • 使用频率计数:这意味着我们假设一个词的重要性与每个词的频率成正比。这是计算重要性的最简单方法。

  • 使用 TFIDF词频-逆文档频率的缩写):这是一个计算每个词在我们试图解决的问题的上下文中重要性的数字。它是两个术语的乘积:

  • 词频TF):这是一个词或术语在文档中出现的次数。词频直接与一个词的重要性相关联。

  • 逆文档频率IDF):首先,文档频率DF)是包含我们搜索的术语的文档数量。作为 DF 的相反,IDF 给出了一个词所代表的独特性的度量,并将其与其重要性相关联。

  • 由于 TF 和 IDF 都量化了我们试图解决的问题的上下文中一个词的重要性,它们的组合 TF-IDF 是每个词的重要性的一个很好的度量,是使用简单频率计数的更复杂的替代方法。

使用共现矩阵

这种方法基于这样的假设:如果两个物品大多数情况下一起购买,那么它们很可能是相似的,或者至少属于通常一起购买的物品类别。

例如,如果人们大多数情况下一起使用剃须膏和剃刀,那么如果有人买了剃刀,建议他也买剃须膏是有道理的。

让我们分析这四个用户的历史购买模式:

剃刀苹果剃须膏自行车鹰嘴豆泥
迈克11101
泰勒10111
埃琳娜00010
阿明10100

这将创建以下共现矩阵:

剃刀苹果剃须膏自行车鹰嘴豆泥
剃刀-1311
苹果1-101
剃须膏31-12
自行车101-1
鹰嘴豆泥1121-

前述共现矩阵总结了一起购买两件物品的可能性。让我们看看如何使用它。

协同过滤推荐引擎

协同过滤的推荐是基于用户的历史购买模式的分析。基本假设是,如果两个用户对大多数相同的物品表现出兴趣,我们可以将两个用户都归类为相似。换句话说,我们可以假设以下内容:

  • 如果两个用户的购买历史重叠超过阈值,我们可以将它们归类为相似用户。

  • 查看相似用户的历史,购买历史中不重叠的物品将成为协同过滤推荐的基础。

例如,让我们看一个具体的例子。我们有两个用户,迈克埃琳娜,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/e1b17be1-78f8-46da-bcba-8a3912708c84.png

请注意以下内容:

  • 迈克和埃琳娜都对文档 1文档 2表现出了完全相同的兴趣。

  • 根据他们相似的历史模式,我们可以将他们两个都归类为相似用户。

  • 如果埃琳娜现在阅读文档 3,那么我们也可以建议迈克阅读文档 3

请注意,根据用户历史记录向用户推荐物品的策略并不总是有效的。

假设埃琳娜和迈克对文档 1都表现出了兴趣,这是关于摄影的(因为他们对摄影有共同的爱好)。此外,他们两个都对文档 2表现出了兴趣,这是关于云计算的,同样是因为他们对这个主题有兴趣。根据协同过滤,我们将他们归类为相似用户。现在埃琳娜开始阅读文档 3,这是一本关于女性时尚的杂志。如果我们遵循协同过滤算法,我们会建议迈克也阅读,而他可能对此并不感兴趣。

回到 2012 年,美国超市 Target 正在尝试使用协同过滤为购买者推荐产品。该算法根据他们的档案将一个父亲归类为他的十几岁的女儿。结果,Target 最终向父亲发送了一个关于尿布、婴儿奶粉和婴儿床的折扣券。他并不知道他女儿怀孕了。

请注意,协同过滤算法不依赖于任何其他信息,是一种独立的算法,基于用户的变化行为和协同推荐。

混合推荐引擎

到目前为止,我们已经讨论了基于内容和基于协同过滤的推荐引擎。这两种类型的推荐引擎可以结合起来创建混合推荐引擎。为此,我们按照以下步骤进行:

  • 生成物品的相似矩阵。

  • 生成用户的偏好矩阵。

  • 生成推荐。

让我们逐步了解这些步骤。

生成物品的相似矩阵

在混合推荐中,我们首先通过使用基于内容的推荐创建物品的相似矩阵。这可以通过使用共现矩阵或使用任何距离度量来量化物品之间的相似性来实现。

假设我们目前有五种物品。使用基于内容的推荐,我们生成一个捕捉物品之间相似性的矩阵,看起来像这样:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/506040b2-df72-412f-bad9-9061bc10a49c.png

让我们看看如何将这个相似矩阵与偏好矩阵结合起来生成推荐。

生成用户的参考向量。

基于系统中每个用户的历史,我们将产生一个捕捉这些用户兴趣的偏好向量。

假设我们想为名为KentStreetOnline的在线商店生成推荐,该商店销售 100 种独特的物品。KentStreetOnline 很受欢迎,拥有 100 万活跃订阅者。重要的是要注意,我们只需要生成一个 100x100 维度的相似矩阵。我们还需要为每个用户生成一个偏好向量;这意味着我们需要为 100 万用户中的每一个生成 100 万个偏好向量。

性能向量的每个条目表示对项目的偏好。第一行的值表示项目 1的偏好权重为4。例如,第二行的值表示对项目 2没有偏好。

以下是图形显示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/c159ced5-502a-4f5e-9092-b364e75b7b15.png

现在,让我们看看如何基于相似矩阵 S 和用户偏好矩阵 U 生成推荐。

生成推荐

为了进行推荐,我们可以将这些矩阵相乘。用户更有可能对经常与他们给出高评分的物品共现的物品感兴趣:

Matrix[S] x Matrix[U] = Matrix[R]

这个计算在以下图表中显示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/ce30323d-894b-4b7a-b4c9-65adb8a387bd.png

为每个用户生成一个单独的结果矩阵。推荐矩阵*Matrix[R]*中的数字量化了用户对每个物品的预测兴趣。例如,在结果矩阵中,第四个物品的数字最高,为 58。因此,这个物品对这个特定用户来说是高度推荐的。

现在,让我们来看看不同推荐系统的局限性。

了解推荐系统的局限性

推荐引擎使用预测算法向一群用户建议推荐。这是一种强大的技术,但我们应该意识到它的局限性。让我们来看看推荐系统的各种局限性。

冷启动问题

显然,为了使协同过滤起作用,我们需要有关用户偏好的历史数据。对于新用户,我们可能没有任何数据,因此我们的用户相似性算法将基于可能不准确的假设。对于基于内容的推荐,我们可能不会立即获得有关新物品的详细信息。需要有关物品和用户的数据来生成高质量推荐的要求被称为冷启动问题

元数据要求

基于内容的方法需要明确的物品描述来衡量相似性。这种明确的详细描述可能不可用,影响预测的质量。

数据稀疏问题

在大量物品中,用户只会对少数物品进行评分,导致非常稀疏的用户/物品评分矩阵。

亚马逊有大约十亿用户和十亿物品。据说亚马逊的推荐引擎是世界上数据最稀疏的推荐引擎。

由于社交影响而产生的偏见

社交影响在推荐系统中可以起到重要作用。社交关系可以被视为影响用户偏好的因素。朋友倾向于购买类似的物品并给出类似的评分。

有限的数据

有限数量的评论使得推荐系统难以准确衡量用户之间的相似性。

实际应用领域

让我们看看推荐系统在实际世界中的应用:

  • Netflix 上的电影有三分之二是推荐的。

  • 亚马逊的三分之一的销售额来自推荐。

  • 在 Google 新闻上,推荐引擎产生的点击率增加了 38%。

  • 尝试预测用户对物品的偏好是基于其他物品的过去评分。

  • 他们可以根据学生的需求和偏好为大学生推荐课程。

  • 他们可以在在线求职门户网站上将简历与工作匹配。

现在,让我们尝试使用推荐引擎来解决一个现实世界的问题。

实际示例 - 创建推荐引擎

让我们构建一个可以向一群用户推荐电影的推荐引擎。我们将使用明尼苏达大学 GroupLens 研究小组收集的数据。

按照以下步骤:

  1. 首先,我们将导入相关的包:
import pandas as pd 
import numpy as np
  1. 现在,让我们导入user_iditem_id数据集:
df_reviews = pd.read_csv('reviews.csv')
df_movie_titles = pd.read_csv('movies.csv',index_col=False)
  1. 我们通过电影 ID 合并了这两个 DataFrame:
df = pd.merge(df_users, df_movie_titles, on='movieId')

在运行上述代码后,df DataFrame 的标题如下:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/25808f2a-8fef-49e7-8e0e-e7f6ec14b479.png

列的详细信息如下:

    • userid:每个用户的唯一 ID
  • 电影 id:每部电影的唯一 ID

  • rating:每部电影的评分从 1 到 5

  • timestamp:电影被评分的时间戳

  • title:电影的标题

  • genres:电影的流派

  1. 为了了解输入数据的摘要趋势,让我们使用groupbytitlerating列计算每部电影的平均评分和评分次数:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/9561fa50-6ff3-4136-bf48-4c1b7c56b208.png

  1. 现在让我们为推荐引擎准备数据。为此,我们将把数据集转换为一个具有以下特征的矩阵:
    • 电影标题将成为列。
  • User_id将成为索引。

  • 评分将是值。

我们将使用 DataFrame 的pivot_table函数来完成它:

movie_matrix = df.pivot_table(index='userId', columns='title', values='rating')

请注意,上述代码将生成一个非常稀疏的矩阵。

  1. 现在,让我们使用我们创建的推荐矩阵来推荐电影。为此,让我们考虑一个特定的用户,他观看了电影Avatar (2009)。首先,我们将找到所有对*Avatar (2009)*表现出兴趣的用户:
Avatar_user_rating = movie_matrix['Avatar (2009)']
Avatar_user_rating = Avatar_user_rating.dropna()
Avatar_user_rating.head()
  1. 现在,让我们尝试推荐与*Avatar (2009)*相关的电影。为此,我们将计算Avatar_user_rating DataFrame 与movie_matrix的相关性,如下所示:
similar_to_Avatar=movie_matrix.corrwith(Avatar_user_rating)
corr_Avatar = pd.DataFrame(similar_to_Avatar, columns=['correlation'])
corr_Avatar.dropna(inplace=True)
corr_Avatar = corr_Avatar.join(df_ratings['number_of_ratings'])
corr_Avatar.head()

这会产生以下输出:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/f0a29140-c9eb-4a17-96ad-f586ea608d86.png

这意味着我们可以将这些电影作为用户的推荐。

总结

在本章中,我们学习了推荐引擎。我们研究了根据我们试图解决的问题选择合适的推荐引擎。我们还研究了如何准备数据以创建相似性矩阵,以供推荐引擎使用。我们还学习了推荐引擎如何用于解决实际问题,比如根据用户过去的模式推荐电影。

在下一章中,我们将专注于用于理解和处理数据的算法。

第三部分:高级主题

正如名称所示,本节将涉及算法的更高级概念。密码学和大规模算法是本节的重点亮点。本节的最后一章,也是本书的最后一章,探讨了在实施这些算法时应该牢记的实际考虑因素。本节包括的章节有:

  • 第十一章,数据算法

  • 第十二章,密码学

  • 第十三章,大规模算法

  • 第十四章,实际考虑因素

第十一章:数据算法

本章讨论了数据中心算法的三个方面:存储、流式处理和压缩。本章首先简要概述了数据中心算法,然后讨论了可以用于数据存储的各种策略。接下来,描述了如何将算法应用于流式数据,然后讨论了压缩数据的不同方法。最后,我们将学习如何使用本章中开发的概念来使用最先进的传感器网络监测高速公路上行驶车辆的速度。

通过本章的学习,您应该能够理解设计各种数据中心算法所涉及的概念和权衡。

本章讨论了以下概念:

  • 数据分类

  • 数据存储算法

  • 如何使用算法来压缩数据

  • 如何使用算法来流式处理数据

让我们首先介绍基本概念。

数据算法简介

无论我们是否意识到,我们生活在一个大数据时代。只需了解一下不断产生的数据量,看一下谷歌在 2019 年发布的一些数字就可以了。众所周知,Google Photos 是谷歌创建的存储照片的多媒体库。2019 年,平均每天有 12 亿张照片和视频上传到 Google Photos。此外,每天平均有 400 小时的视频(相当于 1 PB 的数据)上传到 YouTube。我们可以肯定地说,正在产生的数据量简直是爆炸式增长。

当前对数据驱动算法的兴趣是因为数据包含有价值的信息和模式。如果以正确的方式使用,数据可以成为政策制定、营销、治理和趋势分析的基础。

由于处理数据的算法变得越来越重要,有明显的原因。设计能够处理数据的算法是一个活跃的研究领域。毫无疑问,探索最佳利用数据以提供一些可量化的好处是全世界各种组织、企业和政府的关注焦点。但原始形式的数据很少有用。要从原始数据中挖掘信息,需要对其进行处理、准备和分析。

为此,我们首先需要将其存储在某个地方。高效存储数据的方法变得越来越重要。请注意,由于单节点系统的物理存储限制,大数据只能存储在由高速通信链路连接的多个节点组成的分布式存储中。因此,对于学习数据算法来说,首先看不同的数据存储算法是有意义的。

首先,让我们将数据分类为各种类别。

数据分类

让我们来看看在设计数据算法的背景下如何对数据进行分类。正如在第二章中讨论的那样,算法中使用的数据结构,量化数据的容量、多样性和速度可以用来对其进行分类。这种分类可以成为设计数据算法的基础,用于其存储和处理。

让我们在数据算法的背景下逐个查看这些特征:

  • 容量 量化了需要在算法中存储和处理的数据量。随着容量的增加,任务变得数据密集,并需要足够的资源来存储、缓存和处理数据。大数据是一个模糊定义的术语,用来描述无法由单个节点处理的大量数据。

  • 速度定义了新数据生成的速率。通常,高速数据被称为“热数据”或“热流”,低速数据被称为“冷流”或简单地称为“冷数据”。在许多应用中,数据将是热流和冷流的混合,首先需要准备并合并到一个表中,然后才能与算法一起使用。

  • 多样性指的是需要将不同类型的结构化和非结构化数据合并到一个表中,然后才能被算法使用。

下一节将帮助我们理解涉及的权衡,并在设计存储算法时提出各种设计选择。

数据存储算法介绍

可靠和高效的数据存储库是分布式系统的核心。如果这个数据存储库是为分析而创建的,那么它也被称为数据湖。数据存储库将来自不同领域的数据汇集到一个地方。让我们首先了解分布式存储库中与数据存储相关的不同问题。

了解数据存储策略

在数字计算的最初几年,设计数据存储库的常规方式是使用单节点架构。随着数据集的不断增大,数据的分布式存储现在已经成为主流。在分布式环境中存储数据的正确策略取决于数据的类型、预期的使用模式以及其非功能性需求。为了进一步分析分布式数据存储的需求,让我们从**一致性可用性分区容忍(CAP)**定理开始,这为我们提供了制定分布系统数据存储策略的基础。

介绍 CAP 定理

1998 年,Eric Brewer 提出了一个定理,后来被称为 CAP 定理。它突出了设计分布式存储系统涉及的各种权衡。

为了理解 CAP 定理,首先让我们定义分布式存储系统的以下三个特性:一致性、可用性和分区容忍。CAP 实际上是由这三个特性组成的首字母缩写:

  • 一致性(简称 C):分布式存储由各种节点组成。这些节点中的任何一个都可以用于读取、写入或更新数据存储库中的记录。一致性保证在某个时间t[1],无论我们使用哪个节点来读取数据,我们都会得到相同的结果。每个操作要么返回跨分布式存储库一致的最新数据,要么给出错误消息。

  • 可用性(简称 A):可用性保证分布式存储系统中的任何节点都能立即处理请求,无论是否具有一致性。

  • 分区容忍(简称 P):在分布式系统中,多个节点通过通信网络连接。分区容忍保证在一小部分节点(一个或多个)之间的通信失败的情况下,系统仍然可以正常运行。请注意,为了保证分区容忍,数据需要在足够数量的节点上复制。

使用这三种特性,CAP 定理仔细总结了分布系统的架构和设计中涉及的权衡。具体来说,CAP 定理规定,在存储系统中,我们只能拥有以下两种特性中的两种:一致性或 C,可用性或 A,以及分区容忍性或 P。

这在以下图表中显示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/aed77c3c-5f64-4ccd-9799-36306f2bb941.png

CAP 定理也意味着我们可以有三种类型的分布式存储系统:

  • CA 系统(实现一致性-可用性)

  • AP 系统(实现可用性-分区容忍)

  • CP 系统(实现一致性-分区容忍)

让我们依次来看看它们。

CA 系统

传统的单节点系统是 CA 系统。这是因为如果我们没有分布式系统,那么我们就不需要担心分区容忍性。在这种情况下,我们可以拥有既有一致性又有可用性的系统,即 CA 系统。

传统的单节点数据库,如 Oracle 或 MySQL,都是 CA 系统的例子。

AP 系统

AP 系统是为可用性调整的分布式存储系统。设计为高度响应的系统,它们可以牺牲一致性,以适应高速数据。这意味着这些是设计为立即处理用户请求的分布式存储系统。典型的用户请求是读取或写入快速变化的数据。典型的 AP 系统用于实时监控系统,如传感器网络。

高速分布式数据库,如 Cassandra,是 AP 系统的良好例子。

让我们看看 AP 系统可以在哪些地方使用。如果加拿大交通部想要通过在渥太华一条高速公路上安装的传感器网络监控交通情况,建议使用 AP 系统来实现分布式数据存储。

CP 系统

CP 系统具有一致性和分区容忍性。这意味着这些是调整为在读取过程可以获取值之前保证一致性的分布式存储系统。

CP 系统的典型用例是当我们想要以 JSON 格式存储文档文件时。像 MongoDB 这样的文档数据存储系统是为分布式环境中的一致性而调整的 CP 系统。

分布式数据存储越来越成为现代 IT 基础设施中最重要的部分。分布式数据存储应该根据数据的特性和我们想要解决的问题的要求进行精心设计。将数据存储分类为 CA、AP 和 CP 系统有助于我们理解在设计数据存储系统时涉及的各种权衡。

现在,让我们来看看流数据算法。

呈现流数据算法

数据可以被分类为有界或无界。有界数据是静态数据,通常通过批处理过程处理。流式处理基本上是对无界数据进行数据处理。让我们看一个例子。假设我们正在分析银行的欺诈交易。如果我们想要查找 7 天前的欺诈交易,我们必须查看静态数据;这是一个批处理的例子。

另一方面,如果我们想要实时检测欺诈,那就是流式处理的一个例子。因此,流数据算法是处理数据流的算法。其基本思想是将输入数据流分成批次,然后由处理节点处理。流算法需要具有容错能力,并且应该能够处理数据的传入速度。随着对实时趋势分析的需求增加,对流处理的需求也在这些天增加。请注意,为了使流处理工作,数据必须快速处理,而在设计算法时,这一点必须始终牢记在心。

流应用

流数据及其有意义的利用有许多应用。一些应用如下:

  • 欺诈检测

  • 系统监控

  • 智能订单路由

  • 实时仪表板

  • 高速公路上的交通传感器

  • 信用卡交易

  • 用户在多用户在线游戏中移动

现在,让我们看看如何使用 Python 实现流处理。

呈现数据压缩算法

数据压缩算法参与了减小数据大小的过程。

在这一章中,我们将深入研究一种名为无损压缩算法的特定数据压缩算法。

无损压缩算法

这些算法能够以一种可以在解压缩时不丢失信息的方式压缩数据。当重要的是在解压缩后检索到确切的原始文件时,它们被使用。无损压缩算法的典型用途如下:

  • 压缩文件

  • 压缩和打包源代码和可执行文件

  • 将大量小文件转换为少量大文件

了解无损压缩的基本技术

数据压缩是基于这样一个原则,即大多数数据使用的位数比其熵所指示的最佳位数多。回想一下,熵是一个用来指定数据所携带信息的术语。这意味着可以有更优化的位表示相同信息。探索和制定更有效的位表示成为设计压缩算法的基础。无损数据压缩利用这种冗余来压缩数据而不丢失任何信息。在 80 年代后期,Ziv 和 Lempel 提出了基于字典的数据压缩技术,可以用于实现无损数据压缩。由于其速度和良好的压缩率,这些技术一炮而红。这些技术被用于创建流行的基于 Unix 的compress工具。此外,普遍存在的gif图像格式使用了这些压缩技术,因为它们可以用较少的位数表示相同的信息,节省了空间和通信带宽。这些技术后来成为开发zip实用程序及其变体的基础。调制解调器中使用的压缩标准 V.44 也是基于它。

现在让我们逐一查看即将到来的部分中的技术。

Huffman 编码

Huffman 编码是压缩数据的最古老方法之一,它基于创建 Huffman 树,用于对数据进行编码和解码。Huffman 编码可以通过利用某些数据(例如字母表的某些字符)在数据流中更频繁地出现这一事实,以更紧凑的形式表示数据内容。通过使用不同长度的编码(对于最常见的字符较短,对于最不常见的字符较长),数据占用的空间更少。

现在,让我们学习一些与 Huffman 编码相关的术语:

  • **编码:**在数据的上下文中,编码表示将数据从一种形式表示为另一种形式的方法。我们希望结果形式简洁。

  • **码字:**编码形式中的特定字符称为码字。

  • **固定长度编码:**每个编码字符,即码字,使用相同数量的位。

  • **可变长度编码:**码字可以使用不同数量的位。

  • **代码评估:**这是每个码字的预期位数。

  • **前缀自由码:**这意味着没有码字是任何其他码字的前缀。

  • **解码:**这意味着可变长度编码必须不受任何前缀的限制。

要理解最后两个术语,您首先需要查看此表:

字符频率固定长度编码可变长度编码
L.450000
M.13001101
N.12010100
X.16011111
Y.091001101
Z.051011100

现在,我们可以推断以下内容:

  • **固定长度编码:**该表的固定长度编码为 3。

  • **可变长度编码:**该表的可变长度编码为45(1) + .13(3) + .12(3) + .16(3) + .09(4) + .05(4) = 2.24

以下图表显示了从上面的例子创建的 Huffman 树:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/87919c43-e9b2-4df9-8d42-adf6b96c29d2.png

请注意,Huffman 编码是将数据转换为 Huffman 树以实现压缩。解码或解压缩将数据恢复到原始格式。

一个实际的例子- Twitter 实时情感分析

据说 Twitter 每秒有近 7000 条关于各种话题的推文。让我们尝试构建一个情感分析器,可以实时捕捉来自不同新闻来源的新闻的情绪。我们将从导入所需的包开始:

  1. 导入所需的包:
import tweepy,json,time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
analyzer = SentimentIntensityAnalyzer()

请注意,我们使用以下两个包:

  1. VADER情感分析,代表Valence Aware Dictionary and Sentiment Reasoner。这是一种流行的基于规则的情感分析工具,专为社交媒体开发。如果您以前从未使用过它,那么您首先需要运行以下命令:
pip install vaderSentiment
  1. Tweepy,这是一个用于访问 Twitter 的基于 Python 的 API。同样,如果您以前从未使用过它,您需要首先运行这个:
pip install Tweepy
  1. 下一步有点棘手。您需要向 Twitter 发出请求,创建一个开发者帐户,以获取对推文的实时流的访问权限。一旦您获得 API 密钥,您可以用以下变量表示它们:
twitter_access_token = <your_twitter_access_token>
twitter_access_token_secret = <your_twitter_access_token_secret>
twitter_consumer_key = <your_consumer_key>
twitter_consumer_secret = <your_twitter_consumer_secret>
  1. 然后,让我们配置Tweepy API 的身份验证。为此,我们需要提供先前创建的变量:
auth = tweepy.OAuthHandler(twitter_consumer_key, twitter_consumer_secret)
auth.set_access_token(twitter_access_token, twitter_access_token_secret)
api = tweepy.API(auth, parser=tweepy.parsers.JSONParser())
  1. 现在是有趣的部分。我们将选择我们想要监控情感分析的新闻来源的 Twitter 句柄。在这个例子中,我们选择了以下新闻来源:
news_sources = ("@BBC", "@ctvnews", "@CNN","@FoxNews", "@dawn_com")
  1. 现在,让我们创建主循环。这个循环将从一个名为array_sentiments的空数组开始,用于保存情感。然后,我们将循环遍历所有五个新闻来源,并收集每个 100 条推文。然后,对于每条推文,我们将计算其极性:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/0270c5ec-21f7-4aa3-b229-4544795df1c4.png

  1. 现在,让我们创建一个图表,显示来自这些个别新闻来源的新闻的极性:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/85779482-ee6d-485a-b4f2-d432095bffba.png

请注意,每个新闻来源都用不同的颜色表示。

  1. 现在,让我们看一下摘要统计信息:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/8f9fb7d1-bc46-4e29-8c1e-3aa6d7390809.png

上述数字总结了情感的趋势。例如,BBC 的情感被发现是最积极的,而加拿大新闻频道 CTVnews 似乎带有最消极的情绪。

摘要

在本章中,我们研究了以数据为中心的算法的设计。我们关注了数据为中心算法的三个方面:存储、压缩和流式处理。

我们研究了数据特征如何决定数据存储设计。我们研究了两种不同类型的数据压缩算法。然后,我们研究了一个实际示例,说明了如何使用数据流算法来计算文本数据流中的单词计数。

在下一章中,我们将研究加密算法。我们将学习如何利用这些算法的力量来保护交换和存储的消息。

第十二章:密码学

本章向您介绍了与密码学相关的算法。我们将首先介绍背景,然后讨论对称加密算法。然后我们将解释消息摘要 5MD5)算法和安全哈希算法SHA),并介绍实施对称算法的局限性和弱点。接下来,我们将讨论非对称加密算法以及它们用于创建数字证书的方式。最后,我们将提供一个总结所有这些技术的实际示例。

通过本章的学习,您将对与密码学相关的各种问题有一个基本的了解。

本章讨论以下主题:

  • 密码学简介

  • 了解密码学技术的类型

  • 示例-部署机器学习模型时的安全问题

让我们先从基本概念开始。

密码学简介

保护秘密的技术已经存在了几个世纪。最早的保护和隐藏数据免受敌人侵害的尝试可以追溯到埃及的古代铭文,那里使用了只有少数信任的人知道的特殊字母表。这种早期的安全形式被称为模糊,今天仍然以不同的形式使用。为了使这种方法有效,保护字母表的含义至关重要。随后,在一战和二战中,寻找保护重要消息的绝对可靠方法变得非常重要。20 世纪末,随着电子和计算机的引入,开发了复杂的算法来保护数据,从而产生了一个全新的领域,称为密码学。本章讨论了密码学的算法方面。这些算法的目的是允许两个进程或用户之间进行安全数据交换。密码算法找到了使用数学函数来确保所述安全目标的策略。

了解最弱链接的重要性

有时,在构建数字基础设施的安全性时,我们过分强调个体实体的安全性,而忽视了端到端的安全性。这可能导致我们忽视系统中的一些漏洞和弱点,这些漏洞和弱点后来可能被黑客利用来获取敏感数据。要记住的重要一点是,整个数字基础设施的强度取决于它的最弱链接。对于黑客来说,这个最弱链接可以提供对数字基础设施中敏感数据的后门访问。在某个程度上,加固前门而不关闭所有后门并没有太多好处。

随着数字基础设施的算法和技术变得越来越复杂,攻击者也在不断升级他们的技术。要记住的一点是,攻击者入侵数字基础设施最简单的方法之一就是利用这些漏洞来获取敏感信息。

2014 年,对加拿大联邦研究机构——国家研究委员会NRC)的网络进行了一次网络攻击,据估计造成了数亿美元的损失。攻击者能够窃取数十年的研究数据和知识产权材料。他们利用了网页服务器上使用的 Apache 软件中的一个漏洞来获取对敏感数据的访问权限。

在本章中,我们将重点介绍各种加密算法的漏洞。

让我们首先来看看使用的基本术语。

基本术语

让我们先来看看与密码学相关的基本术语:

  • 密码:执行特定加密功能的算法。

  • 明文:可以是文本文件、视频、位图或数字化语音的原始数据。在本章中,我们将表示明文为P

  • 密文:应用加密后得到的混乱文本。在本章中,我们将其表示为C

  • 密码套件:一组或套件的加密软件组件。当两个独立节点想要使用加密交换消息时,它们首先需要就密码套件达成一致。这对于确保它们使用完全相同的加密函数实现非常重要。

  • 加密:将明文P转换为密文C的过程称为加密。在数学上,它表示为encrypt§ = C

  • 解密:将密文转换回明文的过程。在数学上,它表示为decrypt© = P

  • 密码分析:用于分析加密算法强度的方法。分析人员试图在没有秘密访问的情况下恢复明文。

  • 个人可识别信息PII):PII 是指可以单独使用或与其他相关数据一起用于追踪个人身份的信息。一些例子包括受保护的信息,如社会安全号码、出生日期或母亲的婚前姓氏。

了解安全需求

首先了解系统的确切安全需求是很重要的。了解这一点将帮助我们使用正确的加密技术,并发现系统中的潜在漏洞。为了做到这一点,我们首先需要更好地了解系统的需求。为了了解安全需求,我们执行以下三个步骤:

  • 识别实体。

  • 确立安全目标。

  • 了解数据的敏感性。

让我们逐一看看这些步骤。

识别实体

识别实体的一种方法是首先回答以下四个问题,这将帮助我们了解系统在安全环境中的需求:

  • 哪些应用程序需要受到保护?

  • 我们要保护应用程序免受谁的攻击?

  • 我们应该在哪里保护它们?

  • 我们为什么要保护它们?

一旦我们更好地了解这些要求,我们就可以确立我们数字系统的安全目标。

确立安全目标

通常使用加密算法来满足一个或多个安全目标:

  • 认证:简单来说,认证是我们如何证明用户是其所声称的人的方式。通过认证的过程,我们确保用户的身份得到验证。认证的过程始于用户提供其身份。接着是提供只有用户知道的信息,因此只能由他们产生。

  • 机密性:需要受到保护的数据称为敏感数据。机密性是将敏感数据限制为仅授权用户的概念。为了在传输或存储过程中保护敏感数据的机密性,您需要使数据变得不可读,除了授权用户之外。这是通过使用加密算法来实现的,我们将在本章后面讨论。

  • 完整性:完整性是指建立数据在传输或存储过程中没有被任何方式改变的过程。例如,TCP/IP(传输控制协议/互联网协议)使用校验和或循环冗余校验CRC)算法来验证数据的完整性。

  • 不可否认性:不可否认性是指信息发送方收到数据已被接收的确认,接收方收到发送方身份的确认的概念。这提供了无可辩驳的证据,证明了消息的发送或接收,这可以在以后用来证明数据的接收和通信中的故障点。

了解数据的敏感性

了解数据的机密性很重要。我们还需要考虑数据泄露的后果有多严重。数据的分类有助于我们选择正确的加密算法。根据信息的敏感性,有多种分类数据的方式。让我们看看数据分类的典型方式:

  • 公共数据或未分类数据:任何对公众可用的数据。例如,在公司网站或政府信息门户上找到的信息。

  • 内部数据或机密数据:虽然不适合公开,但将这些数据暴露给公众可能不会造成破坏性后果。例如,如果员工的投诉经理的电子邮件被曝光,这可能会让公司尴尬,但可能不会造成破坏性后果。

  • 敏感数据或机密数据:不应该公开的数据,如果暴露给公众,对个人或组织会造成破坏性后果。例如,泄露未来 iPhone 的细节可能会损害苹果的业务目标,并给三星等竞争对手带来优势。

  • 高度敏感数据:也称为绝密数据。这是如果泄露将对组织造成极大损害的信息。这可能包括客户社会安全号码、信用卡号码或其他非常敏感的信息。绝密数据通过多层安全保护,并需要特别许可才能访问。

一般来说,更复杂的安全设计比简单的算法要慢得多。在安全性和系统性能之间取得正确的平衡非常重要。

了解密码的基本设计

设计密码是为了想出一种算法,可以混淆敏感数据,使恶意进程或未经授权的用户无法访问它。尽管随着时间的推移,密码变得越来越复杂,但密码基于的基本原理保持不变。

让我们从一些相对简单的密码开始,这将帮助我们理解加密算法设计中使用的基本原理。

呈现替换密码

替换密码在各种形式上已经使用了数百年。顾名思义,替换密码基于一个简单的概念——以预定的有序方式用其他字符替换明文中的字符。

让我们看看其中涉及的确切步骤:

  1. 首先,我们将每个字符映射到一个替代字符。

  2. 然后,通过替换映射,将明文编码并转换为密文,用密文中的另一个字符替换明文中的每个字符。

  3. 解码时,使用替换映射将明文还原。

让我们看一些例子:

  • 凯撒密码

在凯撒密码中,替换映射是通过用右边的第三个字符替换每个字符来创建的。这个映射在下图中描述:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/91bc4fec-861e-4d78-9050-7f30a9758aa1.png

让我们看看如何使用 Python 实现凯撒密码:

import string
rotation = 3
P = 'CALM'; C=''
for letter in P:
   C = C+ (chr(ord(letter) + rotation))

我们可以看到我们对明文CALM应用了凯撒密码。

让我们用凯撒密码加密后打印密文:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/8edd428d-94b2-436e-ac6f-a1878e094b43.png据说凯撒密码曾被朱利叶斯·凯撒用来与他的顾问交流。

  • 旋转 13ROT13):

ROT13 是另一种基于替换的加密。在 ROT13 中,替换映射是通过用右边的第 13 个字符替换每个字符来创建的。以下图表说明了这一点:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/a44fe40c-b91e-469b-bfcb-efef009e8856.png

这意味着如果ROT13()是实现 ROT13 的函数,那么以下内容适用:

import codecs
P = 'CALM'
C=''
C=codecs.encode(P, 'rot_13')

现在,让我们打印C的编码值:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/49619d35-4c7c-45be-a774-966b96f9a104.png

  • 替换密码的密码分析

替换密码很容易实现和理解。不幸的是,它们也很容易破解。替换密码的简单密码分析表明,如果我们使用英语字母表,那么我们需要确定的是破解密码的旋转量。我们可以逐个尝试英语字母表的每个字母,直到我们能够解密文本。这意味着需要大约 25 次尝试才能重构明文。

现在,让我们看另一种简单密码—置换密码。

理解置换密码

在置换密码中,明文的字符被置换。让我们看一下其中涉及的步骤:

  1. 创建矩阵并选择置换矩阵的大小。它应该足够大,以适应明文字符串。

  2. 通过横向写入字符串的所有字符来填充矩阵。

  3. 在矩阵中垂直读取字符串的所有字符。

让我们看一个例子。

让我们以Ottawa Rocks明文(P)为例。

首先,让我们对P进行编码。为此,我们将使用一个 3 x 4 的矩阵,横向写入明文:

Otta
waRo
cks

read过程将垂直读取字符串,这将生成密码文本—OwctaktRsao

德国人在第一次世界大战中使用了一种名为 ADFGVX 的密码,它同时使用了置换和替换密码。多年后,它被 George Painvin 破解。

因此,这些是一些密码类型。现在,让我们看一些当前使用的密码技术。

理解密码技术的类型

不同类型的密码技术使用不同类型的算法,并在不同的情况下使用。

广义上,密码技术可以分为以下三种类型:

  • 散列

  • 对称

  • 非对称

让我们逐个来看。

使用密码哈希函数

密码哈希函数是一种数学算法,可以用来创建消息的唯一指纹。它从明文中创建一个称为哈希的固定大小的输出。

从数学上看,这看起来是这样的:

C[1] = hashFunction(P[1])

这是解释如下的:

  • P[1] 是表示输入数据的明文。

  • C[1] 是由密码哈希函数生成的固定长度哈希。

这在下图中显示。可变长度数据通过单向哈希函数转换为固定长度哈希:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/36a659e2-2da6-459b-969d-b8400697eb4c.png

哈希函数具有以下五个特征:

  • 它是确定性的。相同的明文生成相同的哈希。

  • 唯一的输入字符串应该生成唯一的输出哈希值。

  • 无论输入消息如何,它都具有固定长度。

  • 明文中的微小变化会生成新的哈希。

  • 它是一个单向函数,这意味着无法从密码文本C[1]生成明文P[1]

如果我们遇到每个唯一消息没有唯一哈希的情况,我们称之为碰撞。也就是说,如果我们有两个文本P[1]P[2],在碰撞的情况下,意味着hashFunction(P[1]) = hashFunction(P[2])

无论使用的哈希算法如何,碰撞都是罕见的。否则,哈希将毫无用处。然而,对于一些应用,不能容忍碰撞。在这些情况下,我们需要使用一个更复杂但生成碰撞哈希值的可能性更小的哈希算法。

实现密码哈希函数

密码哈希函数可以通过使用各种算法来实现。让我们深入了解其中的两种。

理解 MD5 容忍

MD5 是由 Poul-Henning Kamp 于 1994 年开发的,用来替代 MD4。它生成 128 位哈希。MD5 是一个相对简单的算法,容易发生碰撞。在不能容忍碰撞的应用中,不应使用 MD5。

让我们看一个例子。为了在 Python 中生成 MD5 哈希,我们将使用passlib库,这是一个最流行的开源库之一,实现了 30 多种密码哈希算法。如果它还没有安装在您的设备上,请在 Jupyter 笔记本中使用以下代码安装它:

!pip install passlib

在 Python 中,我们可以按照以下方式生成 MD5 哈希:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/32a01072-7c27-4d65-a565-26af8195a8da.png

请注意,MD5 生成 128 位的哈希。

如前所述,我们可以将生成的哈希用作原始文本的指纹,原始文本是myPassword。让我们看看如何在 Python 中实现这一点:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/dd3a883a-f1b2-4fd5-bf4a-35a505809106.png

请注意,对myPassword字符串生成的哈希与原始哈希匹配,生成了一个True值。但是,一旦明文更改为myPassword2,它就返回了False

现在,让我们来看另一个哈希算法——安全哈希算法SHA)。

理解 SHA

SHA 是由国家标准与技术研究所NIST)开发的。让我们看看如何使用 Python 来创建 SHA 算法的哈希:

from passlib.hash import sha512_crypt
sha512_crypt.using(salt = "qIo0foX5",rounds=5000).hash("myPassword")

请注意使用一个名为salt的参数。加盐是在哈希之前添加随机字符的过程。

运行这段代码将给我们带来以下结果:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/0ac664a2-c588-48d5-a4d9-934b566d653f.png

请注意,当我们使用 SHA 算法时,生成的哈希是 512 字节。

加密哈希函数的应用

哈希函数用于在复制文件后检查文件的完整性。为了实现这一点,当文件从源复制到目的地(例如,从 Web 服务器下载时),相应的哈希也会被复制。这个原始哈希,h[original],充当了原始文件的指纹。复制文件后,我们再次从复制的文件版本生成哈希,即h[copied]。如果h[original] = h[copied]—也就是说,生成的哈希与原始哈希匹配—这验证了文件没有改变,并且在下载过程中没有丢失任何数据。我们可以使用任何加密哈希函数,比如 MD5 或 SHA,来为此目的生成哈希。

现在,让我们来看对称加密。

使用对称加密

在密码学中,密钥是一组数字,用于使用我们选择的算法对明文进行编码。在对称加密中,我们使用相同的密钥进行加密和解密。如果用于对称加密的密钥是K,那么对称加密的等式如下:

EK = C

这里,P是明文,C是密文。

对于解密,我们使用相同的密钥K将其转换回P

DK = P

这个过程在下面的图表中显示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/52dabfcd-01bc-4ef2-80ce-c70d455676c8.png

现在,让我们看看如何在 Python 中使用对称加密。

编写对称加密

在本节中,我们将使用 Python 的cryptography包来演示对称加密。它是一个全面的包,实现了许多加密算法,比如对称密码和不同的消息摘要。第一次使用时,我们需要使用pip命令来安装它:

!pip install cryptography

安装完成后,我们现在可以使用该包来实现对称加密,如下所示:

  1. 首先,让我们导入我们需要的包:
import cryptography as crypt
from cryptography.fernet import Fernet
  1. 让我们生成密钥:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/90609769-1a16-48cf-96b8-6fefa3799ab5.png

  1. 现在,让我们打开密钥:
file = open('mykey.key', 'wb')
file.write(key) 
file.close()
  1. 使用密钥,现在让我们尝试加密消息:

file = open('mykey.key', 'rb')
key = file.read() 
file.close()
  1. 现在,让我们使用相同的密钥解密消息:
from cryptography.fernet import Fernet
message = "Ottawa is really cold".encode()

f = Fernet(key)
encrypted = f.encrypt(message)
  1. 让我们解密消息并将其赋给一个名为decrypt的变量:
decrypted = f.decrypt(encrypted)
  1. 现在让我们打印decrypt变量,以验证我们是否能够得到相同的消息:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/556b14e4-1ddb-4956-906a-06c41f96996e.png

让我们看一些对称加密的优势。

对称加密的优势

尽管对称加密的性能取决于所使用的确切算法,但一般来说,它比非对称加密快得多。

对称加密的问题

当两个用户或进程计划使用对称加密进行通信时,它们需要使用安全通道交换密钥。这引发了以下两个问题:

  • 密钥保护:如何保护对称加密密钥。

  • 密钥分发:如何将对称加密密钥从源共享到目的地。

现在,让我们看一下非对称加密。

非对称加密

在 20 世纪 70 年代,非对称加密被设计出来以解决我们在前一节中讨论的对称加密的一些弱点。

非对称加密的第一步是生成两个看起来完全不同但在算法上相关的不同密钥。其中一个被选择为私钥,K[pr],另一个被选择为公钥,K[pu]。在数学上,我们可以表示如下:

EKpr = C

这里,P是明文,C是密文。

我们可以按以下方式解密:

DKpu = P

公钥应该被自由分发,私钥由密钥对的所有者保密。

基本原则是,如果使用其中一个密钥进行加密,解密的唯一方法是使用另一个密钥。例如,如果我们使用公钥加密数据,我们将需要使用另一个密钥来解密它,即私钥。现在,让我们看一下非对称加密的一个基本协议——安全套接字层SSL)/传输层安全性TLS)握手,它负责使用非对称加密在两个节点之间建立连接。

SSL/TLS 握手算法

SSL 最初是为 HTTP 添加安全性而开发的。随着时间的推移,SSL 被更高效、更安全的协议 TLS 所取代。TLS 握手是 HTTP 创建安全通信会话的基础。TLS 握手发生在两个参与实体——客户端服务器之间。此过程如下图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/787e64d9-a0ed-47d9-9c9b-c7409c0926ea.png

TLS 握手在参与节点之间建立了安全连接。以下是涉及此过程的步骤:

  1. 客户端向服务器发送一个“客户端 hello”消息。消息还包含以下内容:
  • 所使用的 TLS 版本

  • 客户端支持的密码套件列表

  • 一个压缩算法

  • 一个由byte_client标识的随机字节字符串

  1. 服务器向客户端发送一个“服务器 hello”消息。消息还包含以下内容:
  • 服务器从客户端提供的列表中选择的密码套件

  • 一个会话 ID

  • 一个由byte_server标识的随机字节字符串

  • 包含服务器公钥的服务器数字证书,由cert_server标识

  • 如果服务器需要客户端身份验证的数字证书或客户端证书请求,客户端服务器请求还包括以下内容:

  • 可接受的 CA 的可区分名称

  • 支持的证书类型

  1. 客户端验证cert_server

  2. 客户端生成一个随机的字节字符串,由byte_client2标识,并使用服务器通过cert_server提供的公钥进行加密。

  3. 客户端生成一个随机的字节字符串,并用自己的私钥进行加密。

  4. 服务器验证客户端证书。

  5. 客户端向服务器发送一个使用秘密密钥加密的“完成”消息。

  6. 为了从服务器端确认这一点,服务器向客户端发送一个使用秘密密钥加密的“完成”消息。

  7. 服务器和客户端现在建立了一个安全通道。他们现在可以交换使用共享秘密密钥对称加密的消息。整个方法如下所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/6a90b582-3e7c-4378-9f8b-63fec0e0db16.png

现在,让我们讨论如何使用非对称加密来创建公钥基础设施PKI),PKI 是为了满足组织的一个或多个安全目标而创建的。

公钥基础设施

非对称加密用于实现 PKI。PKI 是管理组织加密密钥的最流行和可靠的方式之一。所有参与者都信任一个名为 CA 的中央信任机构。CA 验证个人和组织的身份,然后为他们颁发数字证书(数字证书包含个人或组织的公钥副本和其身份),验证与该个人或组织相关联的公钥实际上属于该个人或组织。

它的工作方式是 CA 要求用户证明其身份,对个人和组织遵循不同的标准。这可能涉及简单地验证域名的所有权,也可能涉及更严格的过程,包括身份的物理证明,这取决于用户试图获得的数字证书的类型。如果 CA 确信用户确实是他们声称的人,用户随后通过安全通道向 CA 提供他们的公共加密密钥。CA 使用这些信息创建包含用户身份和他们的公钥信息的数字证书。该证书由 CA 数字签名。用户随后可以向任何想要验证其身份的人展示其证书,而无需通过安全通道发送它,因为证书本身不包含任何敏感信息。接收证书的人不必直接验证用户的身份。该人只需验证证书是否有效,验证 CA 的数字签名,以验证证书中包含的公钥实际上属于证书上命名的个人或组织。

组织的 CA 的私钥是 PKI 信任链中最薄弱的环节。例如,如果冒名顶替者获取了微软的私钥,他们可以通过冒充 Windows 更新在全球数百万台计算机上安装恶意软件。

示例-部署机器学习模型时的安全问题

在第六章中,无监督机器学习算法,我们看了CRISP-DM跨行业标准数据挖掘过程)生命周期,该生命周期指定了训练和部署机器学习模型的不同阶段。一旦模型被训练和评估,最后阶段是部署。如果这是一个关键的机器学习模型,那么我们希望确保它的所有安全目标都得到满足。

让我们分析部署这样一个模型时面临的常见挑战,以及如何使用本章讨论的概念来解决这些挑战。我们将讨论保护我们训练好的模型免受以下三个挑战的策略:

  • 中间人MITM)攻击

  • 冒充

  • 数据篡改

让我们逐个来看。

中间人攻击

我们希望保护我们的模型免受的可能攻击之一是中间人攻击。中间人攻击发生在入侵者试图窃听假定为私人通信的情况下,部署训练好的机器学习模型。

让我们尝试使用一个示例场景来顺序理解中间人攻击。

假设鲍勃和爱丽丝想要使用 PKI 交换消息:

  1. 鲍勃使用{Pr[Bob],Pu[Bob]},爱丽丝使用{Pr[Alice],Pu[Alice]}。鲍勃创建了消息M[Bob],爱丽丝创建了消息M[Alice]。他们希望以安全的方式彼此交换这些消息。

  2. 最初,他们需要交换他们的公钥以建立彼此之间的安全连接。 这意味着鲍勃在发送消息给艾丽斯之前使用Pu[Alice]加密M[Bob]

  3. 假设我们有一个窃听者X,他正在使用{Pr[X],Pu[X]}。 攻击者能够拦截鲍勃和艾丽斯之间的公钥交换,并用自己的公共证书替换它们。

  4. 鲍勃将M[Bob]发送给艾丽斯,使用Pu[X]而不是Pu[Alice]进行加密,错误地认为这是艾丽斯的公共证书。 窃听者X拦截了通信。 它拦截了*M[Bob]消息并使用Pr[Bob]*解密。

这种中间人攻击显示在以下图表中:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/1500b5c1-9b2b-4c0c-8c7c-c1ca674612eb.png

现在,让我们看看如何防止中间人攻击。

如何防止中间人攻击

让我们探讨如何通过引入 CA 来防止中间人攻击到组织中。 假设这个 CA 的名字是 myTrustCA。 数字证书中嵌入了它的公钥,名为Pu[myTrustCA]。 myTrustCA 负责为组织中的所有人,包括艾丽斯和鲍勃签署证书。 这意味着鲍勃和艾丽斯的证书都由 myTrustCA 签署。 在签署他们的证书时,myTrustCA 验证他们确实是他们声称的人。

现在,有了这个新的安排,让我们重新审视鲍勃和艾丽斯之间的顺序交互:

  1. 鲍勃正在使用{Pr[Bob],Pu[Bob]},艾丽斯正在使用{Pr[Alice],Pu[Alice]}。 他们的公钥都嵌入到他们的数字证书中,由 myTrustCA 签名。 鲍勃创建了一条消息M[Bob],艾丽斯创建了一条消息M[Alice]。 他们希望以安全的方式互相交换这些消息。

  2. 他们交换他们的数字证书,其中包含他们的公钥。 只有在证书中嵌入的公钥由他们信任的 CA 签署时,他们才会接受这些公钥。 他们需要交换他们的公钥以建立彼此之间的安全连接。 这意味着鲍勃将使用Pu**[Alice]来加密M**[Bob],然后将消息发送给艾丽斯。

  3. 假设我们有一个窃听者X,他正在使用{Pr[X],Pu[X]}。 攻击者能够拦截鲍勃和艾丽斯之间的公钥交换,并用自己的公共证书*Pu[X]*替换它们。

  4. 鲍勃拒绝X的尝试,因为坏人的数字证书没有被鲍勃信任的 CA 签名。 安全握手被中止,尝试的攻击被记录下来,并且引发了安全异常。

在部署训练好的机器学习模型时,不是艾丽斯,而是一个部署服务器。 鲍勃只有在建立安全通道后才能部署模型,使用先前提到的步骤。

让我们看看如何在 Python 中实现这一点。

首先让我们导入所需的包。

from xmlrpc.client import SafeTransport, ServerProxy
import ssl

现在让我们创建一个可以验证证书的类。


class CertVerify(SafeTransport): 
    def __init__(self, cafile, certfile=None, keyfile=None): 
    SafeTransport.__init__(self) 
    self._ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) 
    self._ssl_context.load_verify_locations(cafile) 
    if cert: 
        self._ssl_context.load_cert_chain(certfile, keyfile) 
    self._ssl_context.verify_mode = ssl.CERT_REQUIRED 

def make_connection(self, host): 
    s = super().make_connection((host, {'context': self._ssl_context})) 
    return s

# Create the client proxy
s = ServerProxy('https://cloudanum.com:15000', transport=VerifyCertSafeTransport('server_cert.pem'), allow_none=True)

让我们看看我们部署的模型可能面临的其他漏洞。

避免伪装

攻击者X假装成授权用户鲍勃,并获得对敏感数据的访问权限,这在这种情况下是训练模型。 我们需要保护模型免受任何未经授权的更改。

保护我们训练模型免受伪装的一种方法是使用授权用户的私钥对模型进行加密。 一旦加密,任何人都可以通过解密授权用户的公钥来读取和利用模型,这在他们的数字证书中找到。 没有人可以对模型进行任何未经授权的更改。

数据和模型加密

一旦模型部署,提供给模型作为输入的实时未标记数据也可能被篡改。训练好的模型用于推断并为这些数据提供标签。为了防止数据被篡改,我们需要保护静态数据和通信中的数据。为了保护静态数据,可以使用对称加密进行编码。可以建立基于 SSL/TLS 的安全通道来传输数据,以提供安全的隧道。这个安全隧道可以用来传输对称密钥,并且数据可以在提供给训练好的模型之前在服务器上解密。

这是保护数据免受篡改的更有效和可靠的方法之一。

在将模型部署到服务器之前,也可以使用对称加密对模型进行加密。这将防止在部署之前未经授权访问模型。

让我们看看如何使用以下步骤在源处使用对称加密加密训练好的模型,然后在目的地解密它,然后再使用它:

  1. 让我们首先使用鸢尾花数据集训练一个简单的模型:
import cryptography as crypt
from sklearn.linear_model 
import LogisticRegression 
from cryptography.fernet 
import Fernet from sklearn.model_selection 
import train_test_split 
from sklearn.datasets import load_iris 
iris = load_iris() 

X = iris.data 
y = iris.target    
X_train, X_test, y_train, y_test = train_test_split(X, y) 
model = LogisticRegression() 
model.fit(X_train, y_train)
  1. 现在,让我们定义将存储模型的文件的名称:
filename_source = 'myModel_source.sav' 
filename_destination = "myModel_destination.sav" 
filename_sec = "myModel_sec.sav"

请注意,filename_source 是将在源处存储训练好的未加密模型的文件。filename_destination 是将在目的地存储训练好的未加密模型的文件,filename_sec 是加密的训练好的模型。

  1. 我们将使用 pickle 将训练好的模型存储在文件中:
from pickle import dump dump(model, open(filename_source, 'wb'))
  1. 让我们定义一个名为 write_key() 的函数,它将生成一个对称密钥并将其存储在名为 key.key 的文件中:
def write_key():
     key = Fernet.generate_key()
     with open("key.key", "wb") as key_file:
         key_file.write(key)
  1. 现在,让我们定义一个名为 load_key() 的函数,它可以从 key.key 文件中读取存储的密钥:
def load_key():
    return open("key.key", "rb").read()
  1. 接下来,让我们定义一个 encrypt() 函数,它可以加密和训练模型,并将其存储在名为 filename_sec 的文件中:
def encrypt(filename, key):
     f = Fernet(key)
     with open(filename_source,"rb") as file:         
         file_data = file.read() 
     encrypted_data = f.encrypt(file_data)
     with open(filename_sec,"wb") as file:
         file.write(encrypted_data)
  1. 我们将使用这些函数生成对称密钥并将其存储在文件中。然后,我们将读取此密钥并使用它将我们的训练好的模型存储在名为 filename_sec 的文件中:
write_key()
encrypt(filename_source,load_key())

现在模型已经加密。它将被传输到目的地,在那里将用于预测。

  1. 首先,我们将定义一个名为 decrypt() 的函数,我们可以使用它来使用存储在 key.key 文件中的密钥将模型从 filename_sec 解密到 filename_destination
def decrypt(filename, key):
     f = Fernet(key)     
     with open(filename_sec, "rb") as file:                
         encrypted_data = file.read()        
     decrypted_data = f.decrypt(encrypted_data)     
     with open(filename_destination, "wb") as file:         file.write(decrypted_data)
  1. 现在让我们使用这个函数来解密模型并将其存储在名为 filename_destination 的文件中:
decrypt(filename_sec,load_key())
  1. 现在让我们使用这个未加密的文件来加载模型并用于预测:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/62b061a9-c024-47b5-b3fa-8c4cbe41f146.png

请注意,我们已经使用对称加密对模型进行了编码。如果需要,可以使用相同的技术来加密数据。

摘要

在本章中,我们学习了加密算法。我们首先确定了问题的安全目标。然后讨论了各种加密技术,还研究了 PKI 基础设施的细节。最后,我们研究了不同的方法来保护训练好的机器学习模型免受常见攻击。现在,您应该能够理解用于保护现代 IT 基础设施的安全算法的基本原理。

在下一章中,我们将研究设计大规模算法。我们将研究设计和选择大型算法涉及的挑战和权衡。我们还将研究使用 GPU 和集群来解决复杂问题。

第十三章:大规模算法

大规模算法旨在解决庞大的复杂问题。大规模算法的特征是由于其数据规模和处理要求的缘故,需要多个执行引擎。本章首先讨论了什么类型的算法最适合并行运行。然后,讨论了与并行化算法相关的问题。接下来,介绍了计算统一设备架构CUDA)架构,并讨论了如何使用单个图形处理单元GPU)或一组 GPU 来加速算法。还讨论了需要对算法进行哪些更改才能有效利用 GPU 的性能。最后,本章讨论了集群计算,并讨论了 Apache Spark 如何创建弹性分布式数据集RDDs)以创建标准算法的极快并行实现。

在本章结束时,您将能够理解与设计大规模算法相关的基本策略。

本章涵盖了以下主题:

  • 大规模算法介绍

  • 并行算法的设计

  • 利用 GPU 的算法

  • 利用集群计算理解算法

  • 如何利用 GPU 运行大规模算法

  • 如何利用集群的能力运行大规模算法

让我们从介绍开始。

大规模算法介绍

人类喜欢受到挑战。几个世纪以来,各种人类创新使我们能够以不同的方式解决真正复杂的问题。从预测蝗虫袭击的下一个目标区域到计算最大的质数,为我们周围的复杂问题提供答案的方法不断发展。随着计算机的出现,我们发现了一种强大的解决复杂算法的新方法。

定义良好的大规模算法

良好设计的大规模算法具有以下两个特征:

  • 它旨在使用现有资源池最佳地处理大量数据和处理需求。

  • 它是可扩展的。随着问题变得更加复杂,它可以通过提供更多资源来处理复杂性。

实现大规模算法的一种最实用的方法是使用分而治之的策略,即将较大的问题分解为可以独立解决的较小问题。

术语

让我们来看看一些用于量化大规模算法质量的术语。

延迟

延迟是执行单个计算所需的端到端时间。如果*Compute[1]表示从t[1]开始到t[2]*结束的单个计算,则我们可以说以下内容:

延迟 = t[2]-t[1]

吞吐量

在并行计算的背景下,吞吐量是可以同时执行的单个计算的数量。例如,如果在*t[1]*时,我们可以同时执行四个计算,C[1]C[2]C[3]C[4],那么吞吐量为四。

网络双分带宽

网络中两个相等部分之间的带宽称为网络双分带宽。对于分布式计算要有效工作,这是最重要的参数。如果我们没有足够的网络双分带宽,分布式计算中多个执行引擎的可用性带来的好处将被慢速通信链路所掩盖。

弹性

基础设施对突然增加的处理需求做出反应并通过提供更多资源来满足需求的能力称为弹性。

三大云计算巨头,谷歌、亚马逊和微软可以提供高度弹性的基础设施。由于它们共享资源池的巨大规模,很少有公司有潜力与这三家公司的基础设施弹性相匹敌。

如果基础设施是弹性的,它可以为问题创建可扩展的解决方案。

并行算法的设计

重要的是要注意,并行算法并不是万能的。即使设计最好的并行架构也可能无法达到我们期望的性能。广泛使用的一个定律来设计并行算法是安达尔定律。

安达尔定律

Gene Amdahl 是 20 世纪 60 年代研究并行处理的第一批人之一。他提出了安达尔定律,这个定律至今仍然适用,并可以成为理解设计并行计算解决方案时涉及的各种权衡的基础。安达尔定律可以解释如下:

它基于这样一个概念,即在任何计算过程中,并非所有过程都可以并行执行。将会有一个无法并行化的顺序部分。

让我们看一个具体的例子。假设我们想要读取存储在计算机上的大量文件,并使用这些文件中的数据训练机器学习模型。

整个过程称为 P。很明显,P 可以分为以下两个子过程:

  • P1:扫描目录中的文件,创建与输入文件匹配的文件名列表,并传递它。

  • P2:读取文件,创建数据处理管道,处理文件并训练模型。

进行顺序过程分析

运行P的时间由T[seq]**§表示。运行P1P2的时间由TseqTseq表示。很明显,当在单个节点上运行时,我们可以观察到两件事:

  • P2P1完成之前无法开始运行。这由P1 --> P2表示

  • Tseq = Tseq + Tseq

假设 P 在单个节点上运行需要 10 分钟。在这 10 分钟中,P1 需要 2 分钟运行,P2 需要 8 分钟在单个节点上运行。如下图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/28852de3-0a22-4dbc-877e-ac90073c894e.png

现在要注意的重要事情是P1的性质是顺序的。我们不能通过并行化来加快它。另一方面,P2可以很容易地分成可以并行运行的并行子任务。因此,我们可以通过并行运行它来加快运行速度。

使用云计算的主要好处是拥有大量资源池,其中许多资源可以并行使用。使用这些资源解决问题的计划称为执行计划。安达尔定律被广泛用于识别给定问题和资源池的瓶颈。

进行并行执行分析

如果我们想要使用多个节点加速P,它只会影响P2,乘以一个大于 1 的因子s>1

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/fa78d698-b932-406a-9a95-6bb07cebff10.png

过程 P 的加速可以很容易地计算如下:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/e9f45310-5be0-4c7b-b3e3-37f68fc8ba09.png

进程的可并行部分与其总体的比例由b表示,并计算如下:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/b0a3962b-be67-47a2-8678-7d948493ec94.png

例如,在前面的情景中,b = 8/10 = 0.8

简化这些方程将给我们安达尔定律:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/65eafd8e-1313-4267-b1c7-b65390f7e3fa.png

在这里,我们有以下内容:

  • P是整个过程。

  • bP的可并行部分的比例。

  • s是在P的可并行部分实现的加速。

假设我们计划在三个并行节点上运行过程 P:

  • P1是顺序部分,不能通过使用并行节点来减少。它将保持在 2 秒。

  • P2现在需要 3 秒而不是 9 秒。

因此,P的总运行时间减少到 5 秒,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/26d817e9-bdb3-4a6e-98cd-7ef4073ea5d4.png

在前面的例子中,我们可以计算以下内容:

  • n[p] = 处理器的数量 = 3

  • b = 并行部分 = 9/11 = 81.81%

  • s = 速度提升 = 3

现在,让我们看一个典型的图表,解释阿姆达尔定律:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/6075c179-8dae-4a80-aa66-27dd2f492939.png

在前面的图表中,我们绘制了不同b值的sn[p]之间的图表。

理解任务粒度

当我们并行化算法时,一个更大的任务被分成多个并行任务。确定任务应该被分成的最佳并行任务数量并不总是直截了当的。如果并行任务太少,我们将无法从并行计算中获得太多好处。如果任务太多,那么将会产生太多的开销。这也是一个被称为任务粒度的挑战。

负载平衡

在并行计算中,调度程序负责选择执行任务的资源。在没有实现最佳负载平衡的情况下,资源无法充分利用。

局部性问题

在并行处理中,应该避免数据的移动。在可能的情况下,应该在数据所在的节点上本地处理数据,否则会降低并行化的质量。

在 Python 中启用并发处理

在 Python 中启用并行处理的最简单方法是克隆一个当前进程,这将启动一个名为子进程的新并发进程。

Python 程序员,虽然不是生物学家,但已经创造了他们自己的克隆过程。就像克隆的羊一样,克隆副本是原始过程的精确副本。

制定多资源处理策略

最初,大规模算法是在称为超级计算机的巨大机器上运行的。这些超级计算机共享相同的内存空间。资源都是本地的——物理上放置在同一台机器上。这意味着各种处理器之间的通信非常快,它们能够通过共同的内存空间共享相同的变量。随着系统的发展和运行大规模算法的需求增长,超级计算机演变成了分布式共享内存DSM),其中每个处理节点都拥有一部分物理内存。最终,发展出了松散耦合的集群,依赖处理节点之间的消息传递。对于大规模算法,我们需要找到多个并行运行的执行引擎来解决复杂的问题:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/6b9c2b35-14a6-4162-a4d8-6b6a6cd4274f.png

有三种策略可以拥有多个执行引擎:

  • 向内寻找:利用计算机上已有的资源。使用 GPU 的数百个核心来运行大规模算法。

  • 向外寻找:使用分布式计算来寻找更多的计算资源,这些资源可以共同用于解决手头的大规模问题。

  • 混合策略:使用分布式计算,并在每个节点上使用 GPU 或 GPU 阵列来加速算法的运行。

介绍 CUDA

GPU 最初是为图形处理而设计的。它们被设计来满足处理典型计算机的多媒体数据的优化需求。为此,它们开发了一些特性,使它们与 CPU 有所不同。例如,它们有成千上万的核心,而 CPU 核心数量有限。它们的时钟速度比 CPU 慢得多。GPU 有自己的 DRAM。例如,Nvidia 的 RTX 2080 有 8GB 的 RAM。请注意,GPU 是专门的处理设备,没有通用处理单元的特性,包括中断或寻址设备的手段,例如键盘和鼠标。以下是 GPU 的架构:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/3f8ba45d-46a9-4345-b8ff-08ef34554310.png

GPU 成为主流后不久,数据科学家开始探索 GPU 在高效执行并行操作方面的潜力。由于典型的 GPU 具有数千个 ALU,它有潜力产生数千个并发进程。这使得 GPU 成为优化数据并行计算的架构。因此,能够执行并行计算的算法最适合于 GPU。例如,在视频中进行对象搜索,GPU 的速度至少比 CPU 快 20 倍。图算法在第五章 图算法中讨论过,已知在 GPU 上比在 CPU 上运行得快得多。

为了实现数据科学家充分利用 GPU 进行算法的梦想,Nvidia 在 2007 年创建了一个名为 CUDA 的开源框架,全称为 Compute Unified Device Architecture。CUDA 将 CPU 和 GPU 的工作抽象为主机和设备。主机,即 CPU,负责调用设备,即 GPU。CUDA 架构有各种抽象层,可以表示为以下形式:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/fe199de5-75f7-4fa7-8815-0eb6a9630652.png

请注意,CUDA 在 Nvidia 的 GPU 上运行。它需要在操作系统内核中得到支持。最近,Windows 现在也得到了全面支持。然后,我们有 CUDA Driver API,它充当编程语言 API 和 CUDA 驱动程序之间的桥梁。在顶层,我们支持 C、C+和 Python。

在 CUDA 上设计并行算法

让我们更深入地了解 GPU 如何加速某些处理操作。我们知道,CPU 设计用于顺序执行数据,这导致某些类别的应用程序运行时间显著增加。让我们以处理尺寸为 1,920 x 1,200 的图像为例。可以计算出有 2,204,000 个像素需要处理。顺序处理意味着在传统 CPU 上处理它们需要很长时间。像 Nvidia 的 Tesla 这样的现代 GPU 能够产生惊人数量的 2,204,000 个并行线程来处理像素。对于大多数多媒体应用程序,像素可以独立地进行处理,并且会实现显著加速。如果我们将每个像素映射为一个线程,它们都可以在 O(1)常数时间内进行处理。

但图像处理并不是唯一可以利用数据并行性加速处理的应用。数据并行性可以用于为机器学习库准备数据。事实上,GPU 可以大大减少可并行化算法的执行时间,包括以下内容:

  • 为比特币挖矿

  • 大规模模拟

  • DNA 分析

  • 视频和照片分析

GPU 不适用于单程序,多数据SPMD)。例如,如果我们想要计算一块数据的哈希值,这是一个无法并行运行的单个程序。在这种情况下,GPU 的性能会较慢。

我们想要在 GPU 上运行的代码使用特殊的 CUDA 关键字标记为内核。这些内核用于标记我们打算在 GPU 上并行处理的函数。基于这些内核,GPU 编译器分离出需要在 GPU 和 CPU 上运行的代码。

在 Python 中使用 GPU 进行数据处理

GPU 在多维数据结构的数据处理中非常出色。这些数据结构本质上是可并行化的。让我们看看如何在 Python 中使用 GPU 进行多维数据处理:

  1. 首先,让我们导入所需的 Python 包:
import numpy as np
import cupy as cp
import time
  1. 我们将使用 NumPy 中的多维数组,这是一个传统的使用 CPU 的 Python 包。

  2. 然后,我们使用 CuPy 数组创建一个多维数组,它使用 GPU。然后,我们将比较时间:

### Running at CPU using Numpy
start_time = time.time()
myvar_cpu = np.ones((800,800,800))
end_time = time.time()
print(end_time - start_time)

### Running at GPU using CuPy
start_time = time.time()
myvar_gpu = cp.ones((800,800,800))
cp.cuda.Stream.null.synchronize()
end_time = time.time()
print(end_time - start_time)

如果我们运行这段代码,它将生成以下输出:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/20d041ea-1df5-4075-898e-dd19cfe68e37.png

请注意,使用 NumPy 创建此数组大约需要 1.13 秒,而使用 CuPy 只需要大约 0.012 秒,这使得在 GPU 中初始化此数组的速度快了 92 倍。

集群计算

集群计算是实现大规模算法并行处理的一种方式。在集群计算中,我们有多个通过高速网络连接的节点。大规模算法被提交为作业。每个作业被分成各种任务,并且每个任务在单独的节点上运行。

Apache Spark 是实现集群计算的最流行方式之一。在 Apache Spark 中,数据被转换为分布式容错数据集,称为Resilient Distributed DatasetsRDDs)。RDDs 是 Apache Spark 的核心抽象。它们是不可变的元素集合,可以并行操作。它们被分割成分区,并分布在节点之间,如下所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/88c9702a-23ec-45b0-a223-4050254b50e1.png

通过这种并行数据结构,我们可以并行运行算法。

在 Apache Spark 中实现数据处理

让我们看看如何在 Apache Spark 中创建 RDD 并在整个集群上运行分布式处理:

  1. 为此,首先,我们需要创建一个新的 Spark 会话,如下所示:
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName('cloudanum').getOrCreate()
  1. 一旦我们创建了一个 Spark 会话,我们就可以使用 CSV 文件作为 RDD 的来源。然后,我们将运行以下函数-它将创建一个被抽象为名为df的 DataFrame 的 RDD。在 Spark 2.0 中添加了将 RDD 抽象为 DataFrame 的功能,这使得处理数据变得更加容易:
df = spark.read.csv('taxi2.csv',inferSchema=True,header=True)

让我们来看看 DataFrame 的列:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/ffc20c87-5cdf-4c78-a235-2483f9c50655.png

  1. 接下来,我们可以从 DataFrame 创建一个临时表,如下所示:
df.createOrReplaceTempView("main")
  1. 一旦临时表创建完成,我们就可以运行 SQL 语句来处理数据:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/dada91d3-357d-4523-9c0d-3fb41a6d2691.png

需要注意的重要一点是,尽管它看起来像一个常规的 DataFrame,但它只是一个高级数据结构。在幕后,它是将数据分布到整个集群的 RDD。同样,当我们运行 SQL 函数时,在幕后,它们被转换为并行转换器和减少器,并充分利用集群的能力来处理代码。

混合策略

越来越多的人开始使用云计算来运行大规模算法。这为我们提供了结合向外看向内看策略的机会。这可以通过在多个虚拟机中配置一个或多个 GPU 来实现,如下面的屏幕截图所示:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/3c81bb15-6d1a-4f10-9bd7-a575497280e4.png

充分利用混合架构是一项非常重要的任务。首先将数据分成多个分区。在每个节点上并行化需要较少数据的计算密集型任务在 GPU 上进行。

总结

在本章中,我们研究了并行算法的设计以及大规模算法的设计问题。我们研究了使用并行计算和 GPU 来实现大规模算法。我们还研究了如何使用 Spark 集群来实现大规模算法。

在本章中,我们了解了与大规模算法相关的问题。我们研究了与并行化算法相关的问题以及在此过程中可能产生的潜在瓶颈。

在下一章中,我们将探讨实现算法的一些实际方面。

第十四章:实际考虑

本书中介绍的一堆算法可以用于解决现实世界问题。本章是关于本书中介绍的算法的一些实际考虑。

本章的组织如下。我们将从介绍开始。然后,我们将介绍算法可解释性的重要主题,即算法内部机制能否以可理解的方式解释的程度。接下来,我们将介绍使用算法的道德和在实施时可能产生偏见的可能性。然后讨论处理 NP 难问题的技术。最后,我们将探讨在选择算法之前应考虑的因素。

在本章结束时,您将了解在使用算法时需要牢记的实际考虑。

在本章中,我们将涵盖以下主题:

  • 介绍实际考虑

  • 算法的可解释性

  • 理解伦理和算法

  • 减少模型中的偏见

  • 解决 NP 难问题

  • 何时使用算法

让我们从介绍开始,

介绍实际考虑

除了设计、开发和测试算法外,在许多情况下,考虑开始依赖机器解决现实世界问题的某些实际方面也很重要,因为这会使解决方案更有用。对于某些算法,我们可能需要考虑可靠地整合预计会在部署算法后继续变化的新重要信息的方法。整合这些新信息会以任何方式改变我们经过良好测试的算法的质量吗?如果是,我们的设计如何处理?然后,对于一些使用全局模式的算法,我们可能需要关注捕捉全球地缘政治局势变化的实时参数。此外,在某些用例中,我们可能需要考虑在使用时强制执行的监管政策,以使解决方案有用。

当我们使用算法解决现实世界问题时,我们在某种程度上依赖机器进行问题解决。即使是最复杂的算法也是基于简化和假设的,并且无法处理意外情况。我们甚至还远远没有完全将关键决策交给我们设计的算法。

例如,谷歌设计的推荐引擎算法最近面临欧盟的监管限制,原因是隐私问题。这些算法可能是其领域中最先进的。但如果被禁止,这些算法实际上可能会变得无用,因为它们无法用于解决它们本应解决的问题。

事实上,不幸的是,算法的实际考虑仍然是在初始设计阶段通常不考虑的事后想法。对于许多用例来说,一旦算法部署并且提供解决方案的短期激动感过去后,使用算法的实际方面和影响将随着时间的推移被发现,并将定义项目的成功或失败。

让我们看一个实际例子,其中不注意实际考虑导致了一家世界顶尖 IT 公司设计的备受关注的项目失败。

一个 AI Twitter 机器人的悲伤故事

让我们来看看 Tay 的经典例子,它是微软于 2016 年创建的第一个 AI Twitter 机器人。由 AI 算法操作,Tay 应该从环境中学习并不断改进自己。不幸的是,在网络空间生活了几天后,Tay 开始从不断发出的种族主义和粗鲁的推文中学习。它很快开始发表冒犯性的推文。尽管它表现出了智能,并迅速学会根据实时事件创建定制推文,但同时,它严重冒犯了人们。微软将其下线并尝试重新调整,但没有成功。微软最终不得不终止该项目。这是一个雄心勃勃的项目的悲伤结局。

请注意,尽管微软内置的智能令人印象深刻,但该公司忽视了部署自学习 Twitter 机器人的实际影响。NLP 和机器学习算法可能是最好的,但由于明显的缺陷,这实际上是一个无用的项目。如今,Tay 已成为忽视允许算法在飞行中学习的实际影响而导致失败的典型案例。Tay 的失败所带来的教训肯定影响了后来几年的 AI 项目。数据科学家也开始更加关注算法的透明度。这将引出下一个主题,探讨使算法透明的需求和方法。

算法的可解释性

黑匣子算法是指其逻辑由于复杂性或逻辑以混乱的方式表示而无法被人类解释的算法。另一方面,白匣子算法是指其逻辑对人类可见和可理解的算法。换句话说,可解释性帮助人类大脑理解算法为何给出特定结果。可解释性的程度是特定算法对人类大脑可理解的程度。许多类别的算法,特别是与机器学习相关的算法,被归类为黑匣子。如果算法用于关键决策,了解算法产生结果的原因可能很重要。将黑匣子算法转换为白匣子算法还可以更好地了解模型的内部工作。可解释的算法将指导医生哪些特征实际上被用来将患者分类为患病或非患病。如果医生对结果有任何疑问,他们可以回头检查这些特定特征的准确性。

机器学习算法和可解释性

算法的可解释性对于机器学习算法非常重要。在许多机器学习应用中,用户被要求相信模型能帮助他们做出决策。在这种情况下,可解释性在需要时提供透明度。

让我们深入研究一个具体的例子。假设我们想使用机器学习来预测波士顿地区房屋价格,基于它们的特征。还假设当地的城市法规只允许我们使用机器学习算法,只要我们能在需要时提供任何预测的详细信息来进行辩解。这些信息是为了审计目的,以确保房地产市场的某些部分不会被人为操纵。使我们的训练模型可解释将提供这些额外信息。

让我们看看实现我们训练模型可解释性的不同选项。

提供可解释性策略

对于机器学习,基本上有两种策略可以为算法提供可解释性:

  • 全球可解释性策略:这是为了提供模型整体的制定细节。

  • 局部可解释性策略: 这是为我们训练模型所做的一个或多个个体预测提供理由。

对于全局可解释性,我们有诸如概念激活向量测试TCAV)之类的技术,用于为图像分类模型提供可解释性。TCAV 依赖于计算方向导数来量化用户定义的概念与图片分类之间的关系程度。例如,它将量化将一个人分类为男性的预测对图片中面部毛发的敏感程度。还有其他全局可解释性策略,如部分依赖图和计算排列重要性,可以帮助解释我们训练模型中的公式。全局和局部可解释性策略都可以是特定于模型的或模型不可知的。特定于模型的策略适用于某些类型的模型,而模型不可知的策略可以应用于各种模型。

以下图表总结了机器学习可解释性的不同策略:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/508ea77c-e398-4c0a-a06d-d872d52423d3.png

现在,让我们看看如何使用这些策略之一来实施可解释性。

实施可解释性

局部可解释模型不可知解释LIME)是一种模型不可知的方法,可以解释训练模型所做的个体预测。作为模型不可知,它可以解释大多数类型的训练机器学习模型的预测。

LIME 通过对每个实例的输入进行微小更改来解释决策。它可以收集该实例的局部决策边界的影响。它迭代循环以提供每个变量的详细信息。通过查看输出,我们可以看到哪个变量对该实例的影响最大。

让我们看看如何使用 LIME 使我们的房价模型的个体预测变得可解释:

  1. 如果您以前从未使用过 LIME,您需要使用pip安装该软件包:
!pip install lime
  1. 然后,让我们导入我们需要的 Python 软件包:
import sklearn as sk
import numpy as np
from lime.lime_tabular import LimeTabularExplainer as ex
  1. 我们将训练一个能够预测特定城市房价的模型。为此,我们将首先导入存储在housing.pkl文件中的数据集。然后,我们将探索它具有的功能:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/df39d111-43cb-4129-90b9-61e991744bd4.png

基于这些功能,我们需要预测房屋的价格。

  1. 现在,让我们训练模型。我们将使用随机森林回归器来训练模型。首先,我们将数据分为测试和训练分区,然后使用它来训练模型:
from sklearn.ensemble import RandomForestRegressor
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(
    housing.data, housing.target)

regressor = RandomForestRegressor()
regressor.fit(X_train, y_train)
  1. 接下来,让我们识别类别列:
cat_col = [i for i, col in enumerate(housing.data.T)
                        if np.unique(col).size < 10]
  1. 现在,让我们使用所需的配置参数实例化 LIME 解释器。请注意,我们正在指定我们的标签是'price',表示波士顿房屋的价格:
myexplainer = ex(X_train,
    feature_names=housing.feature_names,
    class_names=['price'],
    categorical_features=cat_col,
    mode='regression')
  1. 让我们尝试查看预测的详细信息。首先让我们从 matplotlib 中导入绘图器。
exp = myexplainer.explain_instance(X_test[25], regressor.predict,
        num_features=10)

exp.as_pyplot_figure()
from matplotlib import pyplot as plt
plt.tight_layout()
  1. 由于 LIME 解释器适用于个体预测,我们需要选择要分析的预测。我们已要求解释器解释索引为135的预测的理由:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/68e5f21f-d5d7-4bd4-bd7a-4e78b13b8c87.png

让我们尝试分析 LIME 的上述解释,它告诉我们以下内容:

  • 个体预测中使用的功能列表:它们在前面的截图中显示在y轴上。

  • 决策中功能的相对重要性: 条形的长度越长,重要性越大。数字的值在x轴上。

  • 每个输入功能对标签的正面或负面影响: 红色条表示负面影响,绿色条表示特定功能的正面影响。

理解伦理和算法

通过算法制定模式可能会直接或间接导致不道德的决策。在设计算法时,很难预见潜在的道德影响的全部范围,特别是对于大规模算法,其中可能涉及多个用户。这使得分析人类主观性的影响变得更加困难。

越来越多的公司将算法的道德分析作为其设计的一部分。但事实是,问题可能直到我们发现一个有问题的用例才会变得明显。

学习算法存在的问题

能够根据不断变化的数据模式进行自我调整的算法被称为学习算法。它们处于实时学习模式,但这种实时学习能力可能具有道德影响。这可能导致它们的学习结果在道德上存在问题。由于它们被创建为处于持续进化阶段,几乎不可能对它们进行持续的道德分析。

随着算法复杂性的增加,要完全理解它们对社会中个人和群体的长期影响变得越来越困难。

理解道德考虑

算法解决方案是没有感情的数学公式。负责开发算法的人有责任确保它们符合我们试图解决的问题周围的道德敏感性。这些算法的道德考虑取决于算法的类型。

例如,让我们看看以下算法及其道德考虑。一些需要仔细考虑道德问题的强大算法的例子如下:

  • 分类算法在社会上的使用决定了个人和群体的塑造和管理方式。

  • 在推荐引擎中使用算法时,可以将简历与求职者(个人和群体)进行匹配。

  • 数据挖掘算法用于从用户那里挖掘信息,并提供给决策者和政府。

  • 政府开始使用机器学习算法来决定是否向申请人发放签证。

因此,算法的道德考虑将取决于使用情况以及它们直接或间接影响的实体。在开始使用算法进行关键决策之前,需要从道德角度进行仔细分析。在接下来的部分中,我们将看到在进行算法的仔细分析时应该牢记的因素。

不确定的证据

用于训练机器学习算法的数据可能没有确凿的证据。例如,在临床试验中,由于有限的可用证据,一种药物的有效性可能无法得到证实。同样,可能存在有限的不确定证据表明某个城市的某个邮政编码更有可能涉及欺诈。我们在基于通过这些有限数据找到的数学模式做出决策时应该谨慎。

基于不确定的证据做出的决定很可能导致不合理的行为。

可追溯性

机器学习算法在训练阶段和测试阶段之间的脱节意味着如果算法造成了一些伤害,很难追踪和调试。此外,当发现算法中存在问题时,很难确定受到影响的人。

误导的证据

算法是数据驱动的公式。垃圾进,垃圾出GIGO)原则意味着算法的结果只能像其基础数据一样可靠。如果数据中存在偏见,它们也会反映在算法中。

不公平的结果

算法的使用可能会对已处于不利地位的脆弱社区和群体造成伤害。

此外,已经证明使用算法分配研究资金在多个场合上对男性人口存在偏见。用于授予移民的算法有时会无意中对脆弱人口群体存在偏见。

尽管使用高质量的数据和复杂的数学公式,如果结果是不公平的,那么整个努力可能会带来更多的伤害而不是好处。

减少模型中的偏见

在当前世界中,基于性别、种族和性取向已知的、有充分记录的一般偏见。这意味着我们收集的数据预计会展现出这些偏见,除非我们处理的是一个在收集数据之前已经努力消除这些偏见的环境。

算法中的所有偏见,直接或间接地都是由人类偏见造成的。人类偏见可以体现在算法使用的数据中,也可以体现在算法本身的制定中。对于遵循CRISP-DM跨行业标准流程)生命周期的典型机器学习项目,该生命周期在第五章中有解释,图算法,偏见看起来像这样:

https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/40-algo-shld-know/img/2ae7ab7a-9d40-4e63-97a0-13ce65e941e8.png

减少偏见最棘手的部分是首先识别和定位无意识的偏见。

解决 NP-hard 问题

NP-hard 问题在第四章中得到了广泛讨论,设计算法。一些 NP-hard 问题很重要,我们需要设计算法来解决它们。

如果由于其复杂性或可用资源的限制而发现解决 NP-hard 问题似乎是不可能的,我们可以采取以下其中一种方法:

  • 简化问题

  • 定制一个已知解决方案以解决类似问题

  • 使用概率方法

让我们逐一看看它们。

简化问题

我们可以基于某些假设简化问题。解决的问题仍然给出的解决方案并不完美,但仍然具有洞察力和有用。为了使其起作用,所选择的假设应尽可能不受限制。

示例

在回归问题中,特征和标签之间的关系很少是完全线性的。但在我们通常的操作范围内可能是线性的。将关系近似为线性大大简化了算法,并且被广泛使用。但这也引入了一些影响算法准确性的近似。近似和准确性之间的权衡应该被仔细研究,并且应选择适合利益相关者的正确平衡。

定制一个已知解决方案以解决类似问题

如果已知类似问题的解决方案,那么可以将该解决方案用作起点。它可以定制以解决我们正在寻找的问题。机器学习中的迁移学习TL)就是基于这一原则。其思想是使用已经预先训练的模型的推理作为训练算法的起点。

示例

假设我们想要训练一个二元分类器,它可以基于实时视频流使用计算机视觉在企业培训期间区分苹果和 Windows 笔记本电脑。从视频流中,模型开发的第一阶段将是检测不同的物体并确定哪些物体是笔记本电脑。一旦完成,我们可以进入第二阶段,制定可以区分苹果和 Windows 笔记本电脑的规则。

现在,已经有经过良好训练、经过充分测试的开源模型,可以处理这个模型训练的第一阶段。为什么不以它们作为起点,并将推理用于第二阶段,即区分 Windows 和苹果笔记本电脑?这将使我们有一个快速起步,解决方案在第一阶段已经经过充分测试,因此错误更少。

使用概率方法

我们使用概率方法来获得一个相当不错的解决方案,但并非最佳解决方案。当我们在第七章中使用决策树算法来解决给定问题时,解决方案是基于概率方法的。我们没有证明这是一个最佳解决方案,但它是一个相当不错的解决方案,可以为我们在需求定义中规定的约束条件下提供一个有用的答案。

例子

许多机器学习算法从一个随机解决方案开始,然后迭代地改进解决方案。最终的解决方案可能是有效的,但我们无法证明它是最好的。这种方法用于解决复杂问题,以在合理的时间范围内解决它们。这就是为什么对于许多机器学习算法来说,获得可重复的结果的唯一方法是使用相同的种子来使用相同的随机数序列。

何时使用算法

算法就像从业者工具箱中的工具。首先,我们需要了解在给定情况下哪种工具是最好的。有时,我们需要问自己,我们是否有解决问题的解决方案,以及部署解决方案的正确时间是什么。我们需要确定算法的使用是否能够提供一个实际有用的解决方案,而不是其他替代方案。我们需要分析使用算法的效果,从三个方面来看:

  • 成本:能否证明与实施算法相关的成本?

  • 时间:我们的解决方案是否比更简单的替代方案使整个过程更有效?

  • 准确性:我们的解决方案是否比更简单的替代方案产生更准确的结果?

为了选择正确的算法,我们需要找到以下问题的答案:

  • 我们是否可以通过做出假设来简化问题?

  • 我们将如何评估我们的算法?关键指标是什么?

  • 它将如何部署和使用?

  • 它需要解释吗?

  • 我们是否理解了三个重要的非功能性要求-安全性、性能和可用性?

  • 是否有预期的截止日期?

一个实际的例子-黑天鹅事件

算法输入数据,处理并制定它,并解决问题。如果收集的数据是关于一个极具影响力且非常罕见的事件,我们如何使用由该事件生成的数据以及可能导致大爆炸的事件?让我们在本节中探讨这个方面。

纳西姆·塔勒布在他的 2001 年的书《被随机愚弄》中用黑天鹅事件的比喻来代表这些极其罕见的事件。

在黑天鹅首次在野外被发现之前,几个世纪以来,它们被用来代表不可能发生的事情。在它们被发现后,这个术语仍然很受欢迎,但它所代表的含义发生了变化。现在它代表着一些如此罕见以至于无法预测的事情。

塔勒布提供了将事件分类为黑天鹅事件的四个标准。

将事件分类为黑天鹅事件的四个标准

决定罕见事件是否应该被分类为黑天鹅事件有点棘手。一般来说,为了被归类为黑天鹅,它应该符合以下四个标准。

  1. 首先,一旦事件发生,对观察者来说,它必须是一个令人震惊的惊喜,例如在广岛投下原子弹。

  2. 事件应该是一场轰动一时的事件-一场颠覆性的重大事件,比如西班牙流感的爆发。

  3. 一旦事件发生并尘埃落定,作为观察者群体的数据科学家应该意识到实际上这并不是那么令人惊讶。观察者们从未注意到一些重要的线索。如果他们有能力和主动性,黑天鹅事件本来是可以预测的。例如,西班牙流感爆发之前有一些被忽视的线索。此外,曼哈顿计划在原子弹实际投放广岛之前已经运行了多年。观察者群体只是无法将这些线索联系起来。

  4. 当事件发生时,虽然黑天鹅事件的观察者们感到终身的惊讶,但也许有些人对他们来说根本不是什么惊讶。例如,多年来致力于开发原子弹的科学家们,使用原子能从未是一个惊讶,而是一个预期的事件。

将算法应用于黑天鹅事件

黑天鹅事件与算法相关的主要方面有:

  • 有许多复杂的预测算法可用。但如果我们希望使用标准的预测技术来预测黑天鹅事件作为预防措施,那是行不通的。使用这种预测算法只会提供虚假的安全感。

  • 一旦黑天鹅事件发生,通常不可能准确预测其对包括经济、公众和政府问题在内的更广泛社会领域的影响。首先,作为一种罕见事件,我们没有正确的数据来供给算法,也没有掌握我们可能从未探索和理解的更广泛社会领域之间的相关性和相互作用。

  • 需要注意的一点是,黑天鹅事件并不是随机事件。我们只是没有能力关注最终导致这些事件发生的复杂事件。这是算法可以发挥重要作用的领域。我们应该确保在未来有一种策略来预测和检测这些小事件,这些事件随着时间的推移组合在一起产生了黑天鹅事件。

2020 年初的 COVID-19 爆发是我们这个时代最好的黑天鹅事件的例子。

前面的例子显示了首先考虑和理解我们试图解决的问题的细节,然后提出我们可以通过实施基于算法的解决方案来为解决方案做出贡献的重要性。没有全面的分析,如前所述,使用算法可能只能解决复杂问题的一部分,达不到预期。

总结

在本章中,我们了解了在设计算法时应考虑的实际方面。我们探讨了算法可解释性的概念以及我们可以在不同层面提供它的各种方式。我们还研究了算法中潜在的道德问题。最后,我们描述了在选择算法时要考虑的因素。

算法是我们今天所见证的新自动化世界中的引擎。了解、实验和理解使用算法的影响是很重要的。了解它们的优势和局限性以及使用算法的道德影响将在使这个世界成为一个更好的居住地方方面产生深远影响。这本书正是为了在这个不断变化和发展的世界中实现这一重要目标而做出的努力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值