TowardsDataScience-博客中文翻译-2022-三十二-
TowardsDataScience 博客中文翻译 2022(三十二)
解读 MLOps 的业务考虑因素
原文:https://towardsdatascience.com/interpreting-the-business-considerations-of-mlops-f32613c4bcb4
对云迁移的现实限制的评估

照片由来自 Pexels.com的Kateryna Babaieva 拍摄
让我们想象一下公司 A 。A 公司是典型的中档行业龙头。他们已经有了一个数据科学团队,一个非常成功的 ML 部署,和一个强大的数据基础设施。
此外,与他们的竞争对手相比,他们已经拥有一些云计算和数据工程方面的专家。他们甚至让他们的领导团队致力于“云优先”战略,但很好地为团队提供了实现该目标的灵活性。他们的 IT 安全和数据隐私团队已经参与确保没有客户 PII 数据会泄漏,所以 ML 团队全权负责他们未来的 ML 部署。
公司 A 目前正在寻求通过提高其网络交互的自动化程度来改善其客户关系的质量(包括基于图像和自然语言处理的交互),因此在可预见的未来将会有更多的模型与客户进行交互。由于他们第一个 WidgetBot 的成功,ML 团队和管理层都想继续沿着这条路走下去。
他们的主要云提供商 Awooz 确实为虚拟机提供了 GPU 计算能力。他们最近与 Gupson 有过不愉快的经历,因此团队在失控的成本、有限的能力和供应商锁定方面有点紧张。
他们不承诺交钥匙 MLOps/AutoML 平台,而是希望尝试构建他们的堆栈,以便他们可以控制正在部署的每个模块,这对于团队成员之间的维护和内部知识转移来说是一个优势。
他们需要考虑:
- 他们应该以什么样的顺序实现管道自动化;
- 他们现有部署的自动化程度应该提高多少;和
- 识别控制成本的机会,以避免意外账单。
公司 A 应该如何着手设计其机器学习系统,以在管理运营成本的同时创造最大的客户价值(即,随着时间推移的新模型)?
背景介绍:资本支出和运营支出
资本支出(CapEx)和运营支出(OpEx)是企业两种主要的前瞻性预算机制。资本支出通常用于大规模的资产收购(建筑物和车辆),而运营支出则与日常开支(如工资和软件订阅)相关。

资本支出和运营支出的比较。(作者。)
第一种允许一次性购买价格,第二种允许随着时间的推移吸收成本。另一方面,第一个需要预算批准来为这样的努力开绿灯,而第二个则蚕食部门的盈利能力。
对于公司 A 的情况,“资本支出与运营支出”是评估内部部署与云 IT 基础架构采购的一个很好的代理。他们应该向云提供商租赁节点,还是构建和管理自己的内部设备?
然而,由于云 GPU 实例的定价水平,公司 A 的计划运营支出将明显高于其竞争对手。事实上,根据 Cloud-GPUs.com的说法,一个基于 NVIDIA V100 的虚拟机的月成本可以轻松达到 2000 美元。基于 3 年生命周期价值周期的 IT 采购,其投资回收期甚至不到几个月。在这种情况下,为什么还要考虑云呢?
侧栏:资本支出与运营支出决策的驱动因素是什么?
尽管有许多因素,但通常来说,购买类似产品的资本支出/运营支出决策可归结为预算可用性和资本成本。预算可用性通常每年签核一次(极少数情况下为季度签核)。
资本成本也类似于公司的内部收益率,是公司向自身借款的利率,代表机会成本。通常,大型和/或公共组织的预算内部收益率为 10%至 15%,具体取决于行业和财务状况。由于这一比率,费用只能通过其净现值(NPV)或净未来值(NFV)进行正确的比较。
随着时间的推移租赁设备而不是提前购买设备的考虑是,在运营支出情况下的净现值(此时的感知成本)低于总价值(总和)。
MLOps 的价值
首先,公司 A 应该考虑增加向云的迁移吗?他们有一个职能团队,一个坚实的活动管道,以及一些可以简单重复的可靠成功。他们正以高于市场的趋势增长,所以他们显然已经开足了马力。
与其成长阶段相关的主要问题是计算需求将呈线性增长;更多的模型意味着更多的计算,而更多的计算意味着更多的 IT 管理及其相关的资本负担。如果他们坚持购买更多的本地服务器,那么改进他们的产品很可能意味着他们盈利能力的终结。
不好,遇到更坏的
如果 IT 收购是糟糕的,那么全面的云参与同样是不健康的。由于数据调查和模型测试的探索性质,研发服务器在计算上是贪得无厌的。它们是复杂的系统,需要建立起来,最终会因为闲置而耗费大量资金。
一些不健康的云实践包括:
- 永远在线的 GPU 实例。当培训环境建立起来后,很少会看到团队放弃他们所有的努力。
- 多个 GPU 实例。与始终在线的实例类似,大型团队成员之间的资源共享较差,因此在工作时间需要多个实例。
- 手工安装库。每一个新的实例都需要一个歌舞伎仪式的库装置,只有在一切都设置好之后,项目才能真正开始。
注意:我是第一个同意实例暂停、作业调度和实例克隆是避免这些问题的可靠方法的人。但我也是第一个警告我的客户这些风险的人,并且在最初的几个项目出问题时小心翼翼地再次提供这些建议。内部沟通通常显示出不清楚的团队成员期望,因此需要自动化。
不是“非此即彼”
在过渡阶段,实际上存在一系列的机会,而不是在两种预算策略中强制做出选择。

随着时间的推移,投资自动化可以降低运营成本。(作者。)
通常,随着部署的模型数量的增长,重新训练、打包和部署它们的成本会线性增长。但是,如果相关的自动化同样增加,以驯服这些额外模型的复杂性,那么预算得到控制,同时价值增加。
两全其美:增量迁移
在公司 A 的案例中,他们有后知后觉的优势:他们已经有了一个成功的部署。将这第一次成功作为自动化用例的跳板意味着,如果做得正确,每一个未来的模型都将部署得更快、更便宜。

顺序 MLOps 实施策略。(作者。)
在最终迁移到云的过程中,混合云/内部部署提供了成本管理和灵活性的最终平衡。第一次交付时,端到端的功能性(非自动化)管道已经存在。
下一阶段实际上不应该专注于加速数据科学或提高机器学习性能;相反,所有的部署问题都是团队自动化能量应该去的地方。这包括从自动配置虚拟机到从注册表中获取最新型号的所有内容。关注部署的原因是,在此之前执行的任何自动化都需要重新构建,以适应部署验证和 QA 需求。
面向客户的产品需要大量的努力来确保质量和可靠性。因此,推理模型的包装是最有可能在这一步被破坏的。
从那里,使用相同的逻辑,自动化前面的步骤,一直到数据科学,这就是你的团队如何在规定的时间内创造最大的价值。
鸡肉还是鸡蛋?
虽然宣扬 MLOps 云过渡应侧重于经济高效、可扩展的工作很容易,但我们合作的每个客户都有自己独特的历史和挑战。帮助他们摆脱内部过渡项目是我们最终创造长期价值的方式。
以下是一些我们认为适用于几乎每个团队的一般性建议:
- 最大限度地利用您现有的基础设施。保持云中的预置训练和推理。如果你现场有 GPU,那就好好利用它们。他们是沉没成本,是的,但已经安装和运行。现在还不需要将计算量最大的步骤迁移到云中。
- 按模块和阶段部署自动化活动,而不是按项目。跨步骤重用代码越多,未来项目的规模就越大。
- 尽早构建您的供应自动化脚本。尽管看起来这应该在以后发生,但这给了你的团队信心,在不损失生产力的情况下尽快取消训练和推理实例。
你可能喜欢的其他文章
- PyTorch 与 TensorFlow 在基于变压器的 NLP 应用中的对比
- 用于批处理的 MLOps:在 GPU 上运行气流
- 数据集偏差:制度化歧视还是足够透明?
- AI 如何创造价值?
- 实施企业人工智能战略
- 离群点感知聚类:超越 K 均值
- 深度学习图像分类器的罗夏测试
如果您对本文或我们的 AI 咨询框架有其他问题,请随时通过LinkedIn*或通过* 电子邮件 联系我们。
马特。
解读文本分类的 BERT 模型预测
如何利用积分梯度解释 BERT 模型的预测

Shane Aldendorff 摄影:https://www . pexels . com/photo/shallow-focus-of-photography of-the-magnification-of-glass-with-black-frame-924676/
来自 Transformer 或 BERT 的双向编码器表示是一种在 NLP 领域非常流行的语言模型。BERT 实际上是 NLP 的瑞士军刀,因为它的多功能性以及它在许多不同的 NLP 任务中的表现,如文本分类、命名实体识别、问答等。
但是如果我们使用 BERT 来完成一个特定的 NLP 任务,就会有一个问题:它的架构由一个很深的层堆栈组成。出于这个原因,BERT 通常被认为是一个黑箱模型,因为它的预测结果不容易解释。
假设我们已经训练了我们的 BERT 模型来对电影评论的情绪进行分类。接下来,我们想用它来预测一个随机的电影评论。

作者图片
伯特模型预测“这部电影棒极了”的评论有积极的情绪,这是正确的。然而,之后我们可能会问一些这样的问题:
- 为什么模型预测我们的输入评审是正面评审而不是负面评审?
- 我们的模型发现哪些词在输入评论中最重要,可以归类为正面评论?
- 我们的模型到底有多可靠?
如果我们使用像 BERT 这样的黑盒模型,回答上面的问题并不容易。然而,有一种方法可以解释深度学习模型的预测,以便我们可以回答上面的所有问题。我们要用积分梯度来做这个。
什么是集成渐变?
集成梯度是一种基于模型输出(预测)相对于输入的梯度来计算深度学习模型的每个特征的属性的方法。这种方法适用于任何用于分类和回归任务的深度学习模型。
举个例子,假设我们有一个文本分类模型,我们想要解释它的预测。利用集成的梯度,最终,我们将获得每个输入单词相对于最终预测的属性分数。我们可以使用这个归因分数来找出哪些单词在我们的模型的最终预测中起着重要作用。

作者图片
要实现集成渐变,我们需要两组输入: 原始输入 t 和 基线输入 。
最初的输入是不言自明的。这只是我们最初的输入。同时,基线特征是一个‘空’或‘中性’输入。这方面的例子取决于我们的使用情形,例如:
- 如果我们的输入是一幅图像:基线输入可以是一幅黑色图像(所有像素都设置为 0)
- 如果我们的输入是一个 text:基线输入可以是一个全零嵌入向量

作者图片
基线输入很重要,因为为了找出哪些特征对模型的预测有影响,我们需要比较使用原始输入和使用基线输入时模型预测的变化。
然后,我们逐步对基线输入进行插值以模拟我们的原始输入,同时计算每一步中相对于输入特征的预测梯度。因此,根据以下公式计算积分梯度:

作者图片
**where:**
**i** : feature iterator
**x** : original input
**x'**: baseline input
**k** : scaled feature perturbation
**m** : total number of approximation steps
你可能会注意到,上面的等式只是真实积分梯度的近似值。这是因为在实践中,计算真正的积分在数值上并不总是可能的。
近似步骤的总数( m )越高,近似结果越接近真实的积分梯度。同样,随着缩放特征扰动( k )越接近 m 、)基线输入越类似于原始输入。在实际操作中,我们应该事先定义好 m 的值。
为了更好地理解上述等式的概念,让我们假设我们有一个图像分类模型,因为它更容易可视化。如果我们想用积分梯度来解释它的预测,以下是步骤。:
1。插入基线输入
如前所述,为了实现集成渐变,我们需要两个不同的输入: 原始输入 和 基线输入 。如果我们的输入是图像,那么原始输入将是我们的原始图像,基线输入将是全黑图像。

作者图片

作者图片
然后,该方法将通过基于我们需要预先定义的步骤总数( m )逐步增加 k 值来线性插值基线图像。随着 k 值越接近 m ,我们的基线图像与输入图像将越相同。
假设我们将总步数设置为 50。随着 k 值越来越接近 50,我们的基线图像和我们的输入图像将越来越相同。

作者图片
2.计算梯度
对于每个插值基线图像,该方法将计算模型预测相对于每个输入特征的梯度。
这些梯度测量模型预测相对于输入要素变化的变化,以便最终我们可以估计每个输入要素对模型预测的重要性。

作者图片
3.累积梯度
最后,我们用一种叫做黎曼和的近似方法来累积每一步的梯度。我们基本上只是把每一步的梯度相加,然后除以总步数。

作者图片
经过这一步,我们得到每个特征对模型预测的归属结果。
BERT 的集成梯度
在本文中,我们将实现集成梯度方法来解释为文本分类用例训练的 BERT 模型的预测。
如果您不熟悉 BERT,并且希望对它的功能和架构有一个基本的了解,请在这里查看我的另一篇文章。
简而言之,BERT 架构由堆叠在一起的变压器编码器组成,编码器的数量取决于我们使用的 BERT 模型。
- BERT 基本模型有 12 层变压器编码器
- 伯特大型模型有 24 层的变压器编码器

伯特基地建筑插图(作者图片)
当我们想要使用 BERT 模型来预测文本时,我们通常首先做的是对输入文本进行标记。
标记化过程将我们的输入文本分割成称为标记的更小的块,每个标记由一个单词或一个子单词组成。接下来,BERT 模型的特殊令牌如【CLS】、【SEP】,以及可选的【PAD】将被添加到我们的初始令牌中。最后,每个令牌将被转换成其可以被机器学习算法使用的数字表示。
from transformers import BertTokenizer
# Instantiate tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
text = 'The movie is superb'
# Tokenize input text
text_ids = tokenizer.encode(text, add_special_tokens=True)
# Print the tokens
print(tokenizer.convert_ids_to_tokens(text_ids))
# Output: ['[CLS]', 'The', 'movie', 'is', 'superb', '[SEP]']
# Print the ids of the tokens
print(text_ids)
# Output: [101, 1109, 2523, 1110, 25876, 102]
但是,符号化后的这种数值表示不能用于积分梯度,因为它们是离散的,不能与上一节中描述的插值方法一起使用。因此,我们需要将标记化结果转换成另一种形式。
正如您在上面的 BERT 架构中所看到的,在每个令牌被传递到编码器堆栈之前,需要通过嵌入层将其转换为嵌入。我们将使用每个标记的嵌入作为输入,来计算每个输入对模型预测的贡献。
下面是一个简单的例子,说明如何用 BERT 嵌入令牌。我们将在下一节看到详细的实现。
from transformers import BertModel
import torch
# Instantiate BERT model
model = BertModel.from_pretrained('bert-base-cased')
embeddings = model.embeddings(torch.tensor([text_ids]))
print(embeddings.size())
# Output: torch.Size([1, 6, 768]), since there are 6 tokens in text_ids
用 Captum 实现 BERT 模型的集成梯度
现在我们知道了集成渐变背后的概念,让我们在行动中实现它。作为第一步,我们需要实例化我们的 BERT 模型的架构:
from torch import nn
# Class of model architecture
class BertClassifier(nn.Module):
def __init__(self, dropout=0.5):
super(BertClassifier, self).__init__()
self.bert = BertModel.from_pretrained('bert-base-cased')
self.dropout = nn.Dropout(dropout)
self.linear = nn.Linear(768, 2)
self.relu = nn.ReLU()
def forward(self, input_id, mask = None):
_, pooled_output = self.bert(input_ids= input_id, attention_mask=mask,return_dict=False)
dropout_output = self.dropout(pooled_output)
linear_output = self.linear(dropout_output)
final_layer = self.relu(linear_output)
return final_layer
该体系结构对应于用于训练 BERT 模型以对电影评论的情感进行分类的体系结构。我们使用预先训练的基于 BERT 的模型,然后在末尾添加一个线性层,该层有两个输出来分类电影情绪是消极的还是积极的。
如果您想继续解释该模型,可以通过以下链接下载经过培训的模型:
https://github.com/marcellusruben/bert_captum/raw/main/bert_model.pt
现在,让我们加载已训练模型的参数,然后将已训练模型设置为评估模式。
model = BertClassifier()
model.load_state_dict(torch.load('bert_model.pt', map_location=torch.device('cpu')))
model.eval()
当我们想要使用集成梯度来解释模型的预测时,我们需要指定两件事情:模型的输出和模型的输入。
该模型的输出仅仅是对电影评论的情绪预测。这意味着从模型的最后一层得出的值。为了获得模型的输出,我们可以简单地向前传递。
同时,如前一节所述,模型的输入将是令牌的嵌入。这意味着通过嵌入层后的特征。因此,来自嵌入层的值将是我们模型的输入。
让我们定义模型的输出和输入。
# Define model output
def model_output(inputs):
return model(inputs)[0]
# Define model input
model_input = model.bert.embeddings

作者图片
现在是我们实现集成渐变的时候了,我们将使用 Captum 来实现这一点。Captum 是一个开源库,用于解释 PyTorch 中内置的机器学习预测。这意味着这个库适用于任何 PyTorch 模型。
目前,Captum 支持三种不同的方法来解释任何 PyTorch 模型的结果:
- :评估各特征对模型预测影响的方法
- 层属性 :评估一层神经元对模型预测影响的方法
- 神经元归因: 一种评估每个输入特征对特定神经元激活的影响的方法
既然我们要解释每个表征对模型预测的影响,那么我们就需要实现主属性,而积分梯度就是主属性的算法之一。
我们实际上只需要一行代码来初始化带有 Captum 的集成梯度算法。然后,我们提供模型的输出和输入作为参数。
*from captum.attr import LayerIntegratedGradients
lig = LayerIntegratedGradients(model_output, model_input)*
如果您还记得上一节,集成渐变的实现需要两件事情:一个是我们的原始输入,另一个是基线输入。
因为我们正在处理文本输入,那么我们的原始输入将是每个标记的嵌入。同时,基线输入将是一组填充标记的嵌入,其长度与我们的原始标记相似。让我们创建一个函数来生成这个基线。
*def construct_input_and_baseline(text):
max_length = 510
baseline_token_id = tokenizer.pad_token_id
sep_token_id = tokenizer.sep_token_id
cls_token_id = tokenizer.cls_token_id
text_ids = tokenizer.encode(text, max_length=max_length, truncation=True, add_special_tokens=False)
input_ids = [cls_token_id] + text_ids + [sep_token_id]
token_list = tokenizer.convert_ids_to_tokens(input_ids)
baseline_input_ids = [cls_token_id] + [baseline_token_id] * len(text_ids) + [sep_token_id]
return torch.tensor([input_ids], device='cpu'), torch.tensor([baseline_input_ids], device='cpu'), token_list
text = 'This movie is superb'
input_ids, baseline_input_ids, all_tokens = construct_input_and_baseline(text)
print(f'original text: {input_ids}')
print(f'baseline text: {baseline_input_ids}')
# Output: original text: tensor([[ 101, 1109, 2523, 1110, 25876, 102]])
# Output: baseline text: tensor([[101, 0, 0, 0, 0, 102]])*
执行上面的函数后,我们得到三个变量:input_ids是标记化的原始输入文本,baseline_input_ids是标记化的基线文本,all_tokens是原始输入文本中的标记列表。稍后,我们将仅出于可视化目的使用all_tokens变量。
接下来,我们可以开始解读模型的预测。为此,我们可以使用之前已经初始化的LayerIntegratedGradients()类中的attribute()方法。我们需要做的就是将原始输入和基线输入的标记化结果作为参数传递。可选地,您可以通过传递n_steps变量作为附加参数来指定近似步骤的数量。否则,它将被设置为 50。
*attributions, delta = lig.attribute(inputs= input_ids,
baselines= baseline_input_ids,
return_convergence_delta=True,
internal_batch_size=1
)
print(attributions.size())
# Output: torch.Size([1, 6, 768])*
我们在实现集成梯度后得到两个变量:attributions和delta。
从上一节中,我们已经知道,我们实现的积分梯度法只是一种近似方法,因为计算真正的积分在数值上并不总是可能的。这里我们得到的delta变量是近似的和真实的积分梯度之差。我们稍后将仅出于可视化目的使用该变量。
同时,attributions变量是每个令牌的每个嵌入元素的属性。为了得到每个标记的最终属性,我们需要计算所有嵌入元素的属性平均值。下面的函数就是这样做的。
*def summarize_attributions(attributions):
attributions = attributions.sum(dim=-1).squeeze(0)
attributions = attributions / torch.norm(attributions)
return attributions
attributions_sum = summarize_attributions(attributions)
print(attributions_sum.size())
# Output: torch.Size([6])*
就是这样!上述函数的输出是每个令牌的属性。由于在标记化过程之后我们有六个标记,那么我们也得到六个属性值,每个标记一个值。
为了让我们更容易检查结果,我们可以用 Captum 的VisualizationDataRecord()类和visualize_text()方法来可视化它。
*from captum.attr import visualization as viz
score_vis = viz.VisualizationDataRecord(
word_attributions = attributions_sum,
pred_prob = torch.max(model(input_ids)[0]),
pred_class = torch.argmax(model(input_ids)[0]).numpy(),
true_class = 1,
attr_class = text,
attr_score = attributions_sum.sum(),
raw_input_ids = all_tokens,
convergence_score = delta)
viz.visualize_text([score_vis])*
上面调用VisualizationDataRecord()类时需要供应的参数很多,我们就一个一个解剖吧。
word_attributions:每个令牌的综合渐变结果pred_prob:模型预测pred_class:模型预测的类别true_class:输入的地面实况标签attr_class:输入attr_score:整个代币的 IG 之和raw_input_ids:令牌列表convergence_score:近似积分梯度和真实积分梯度的区别
一旦我们运行上面的代码片段,我们将得到如下可视化结果:

作者图片
从上面的可视化可以看出,预测的评论是 1(正),这是正确的。在“单词重要性部分,我们可以看到每个单词对模型预测的贡献。
对模型预测有积极贡献的标记以绿色突出显示。同时,对模型预测有负面影响的标记以红色突出显示。
似乎单词“ superb ”最有影响力,这使得我们的 BERT 模型将评论分类为积极的,这是有意义的。让我们将目前为止所做的事情封装到一个函数中,然后解释另一个评论。
*def interpret_text(text, true_class):
input_ids, baseline_input_ids, all_tokens = construct_input_and_baseline(text)
attributions, delta = lig.attribute(inputs= input_ids,
baselines= baseline_input_ids,
return_convergence_delta=True,
internal_batch_size=1
)
attributions_sum = summarize_attributions(attributions)
score_vis = viz.VisualizationDataRecord(
word_attributions = attributions_sum,
pred_prob = torch.max(model(input_ids)[0]),
pred_class = torch.argmax(model(input_ids)[0]).numpy(),
true_class = true_class,
attr_class = text,
attr_score = attributions_sum.sum(),
raw_input_ids = all_tokens,
convergence_score = delta)
viz.visualize_text([score_vis])*
仅此而已。现在,如果我们想解释一个电影评论,我们可以做的是键入评论,然后调用上面的interpret_text()函数。
*text = "It's a heartfelt film about love, loss, and legacy"
true_class = 1
interpret_text(text, true_class)*

作者图片
我们的 BERT 模型正确预测了评论情绪,即预测为 1(正面)。从每个令牌的属性中,我们可以看到像'心'、爱'、遗产'这样的词对模型的预测有积极的贡献,而另一方面,'损失'这样的词有消极的贡献。
现在让我们给我们的模型提供一个负面的评论。
*text = "A noisy, hideous, and viciously cumbersome movie"
true_class = 0
interpret_text(text, true_class)*

作者图片
我们的 BERT 模型再次正确预测了电影情绪。从 token attribution 中,我们可以看到像'吵闹的'、狞恶的'、恶毒的'这样的词对模型的预测有积极的贡献,这也是有道理的。
结论
在本文中,我们实现了集成梯度来解释文本分类的 BERT 模型的预测。通过这种方法,我们能够找出哪些输入对我们的深度学习模型进行预测是最重要的。因此,它也将为我们提供关于模型可靠性的信息。
我希望这篇文章能帮助你开始使用集成渐变。你可以在本笔记本 的 中找到本文实现的所有代码。
面试问题:从文件中随机选择一行(Python 中)
一个很酷很有用的算法解释和扩展
卡尔·m·卡迪和克里斯托弗·米克

从文件中随机选择一行文本的 python 来源:【https://openai.com/dall-e-2/
如果我(卡尔)面试你在微软的工作,我可能会问你这个问题:
如何从未知长度的文本文件中随机选择一行?
我在微软研究院,在最初的反垃圾邮件团队,在一个办公室机器学习/数据科学小组,都问过这个问题。我们喜欢这个问题,因为它涉及概率、算法和系统问题。
即使已经从微软退休,我们仍然会思考随机线问题。例如,我们最近学习了[bytecount](https://lib.rs/crates/bytecount)。这是一个 Rust crate,它使用 SIMD CPU 指令来加速计算文件中的行数。正如我们稍后将描述的,我们对板条箱的学习间接导致了我们对随机线问题最喜欢的解决方案的改进。
我们将这篇文章组织成一系列的问题和提示。如果你愿意,在看到我们的回答之前,你可以试着自己回答问题。
在本文中,我们将用 Python 给出答案。Rust 中的答案见本文的 Rust 版。你可能也喜欢阅读这两个版本,作为比较两种语言的一种方式。
对于白板面试,我们给了受访者以下建议:
- 请随意提出澄清性问题。(在本文的上下文中,您可以进一步阅读,看看我们是否提供了澄清。)
- 首先从正确的算法开始,即使它可能不是最佳的。如果我们想要一个更好的算法,我们会用后续问题来提示您。
- 不要担心精确的语法。例如,我们不关心你是否记得产生随机数的确切方法。(在本文中,我们将用 Python 给出答案。)
- 如果你卡住了,我们可以提供一些提示。(还是那句话,在这篇文章的上下文中,读远一点寻找提示。)
让我们从问题开始:
问:如何从未知长度的文本文件中随机选择一行?
答:我们首先要明确一个“随机线”的含义。我们的意思是文本文件中的每一行都有相等的机会被返回。换句话说,我们希望算法通过均匀分布在这些线中进行选择。
这个要求意味着你不能使用这个算法,我们称之为AlgoRandomSeek:
- 向文件系统询问文件的长度。
- 随机选择一个文件位置并查找到该位置。
- 在位置附近返回一条线。
Sub Q:AlgoRandomSeek怎么了?
Sub A :虽然该算法可以返回文件中的任何行,但它返回较长行的概率高于较短行,因此不会通过均匀分布选择行。
旁白:我们喜欢被要求澄清。它将“随机”的日常含义与统计学、数据科学、决策理论和机器学习中使用的技术含义区分开来。
提示:如果你在用 Python 编程,需要快速(但正确地)解决随机线问题,你会怎么做?
我们向 GitHub Copilot 寻求解决方案,它给了我们这段代码。称之为AlgoReadAll:
这显然是正确和快速的。另外,它使用with open(file_name) as f:在上下文管理器中打开文件,这是很好的实践。为了获得额外的好处,您可以提到代码在空文件上抛出一个异常。
不利的一面是,这段代码将整个文件读入内存,因此不能处理大文件。另外(次要一点),f.read().splitlines()可以换成f.readlines()。
旁白:你可以用一行代码编写这个算法:
random.choice(open(file_name).readlines())。然而,我们不喜欢伤害可读性的单行解决方案。
接下来,让我们询问一个适用于大型文件的解决方案:
问:使用很少的内存,如何从未知长度的文本文件中随机选择一行?
答:首先要明确“小内存”。对于这个问题,假设您可以存储文件中的任何一行,或者几行,但不是所有的行。
AlgoTwoPass用 1 解决问题。计算文件中的行数,2。随机选择一个行索引,3 .返回带有该索引的行。(在本文中,“索引 0”是一个从 0 开始计数的索引。如果一个索引从 1 开始计数,我们称之为“index1”。)
在我的机器上,这将输出:
100,989 of 146,933: made in 1875 and the number of patents soon rapidly increased;
AlgoTwoPass正确。我还要给这段代码加分,因为:
- 使用带有显式种子的随机数生成器—机器学习和数据科学通常需要再现性,即使是来自随机性。
- 【非常小】使用 Python 格式字符串,包括千位分隔符。
- 提到它在应用于空文件时返回
None。
不是必需的,但有趣的是这个更具功能性的实现:
旁白 1: 背景信息:这段代码使用了 Python 迭代器。当
next()应用于 Python 迭代器时,返回序列中的下一个值,或者,如果序列完成,抛出异常。itertools.islice函数可以在不返回值的情况下在迭代器中向前跳转。表达式open(file_name)返回一个对象,该对象是文件中各行的迭代器。enumerate()函数接受一个迭代器并返回一个新的迭代器,这样每次调用新迭代器上的next()都会返回一个从 0 开始的索引和一个来自原始迭代器的值。旁白 2:我们不会因为某人用
rng.randint或itertools.islice犯了一个一分的错误而惩罚他。然而,我们可能会问,如何测试代码来检查这样的错误。
接下来,我们要求一个更快的算法:
问:在一次中,你能从未知长度的文本文件中随机选择一行吗?
答:经过提示,我们将通过一系列子问题开发一个算法。
提示:递归地想想这个。
Sub Q: 把自己放在程序的位置上。如果我们向您保证文件只包含一行,您应该怎么做?
如果文件只包含一行,就输出它。
子问:呜呜,我们骗了你。该文件可能只包含一行或两行,但没有其他数量的行。你该怎么办?
Sub A: 用概率 0.5,替换我们要输出的结果,用第二行。
子问:对不起,我们又说谎了!该文件可能只包含一行、两行或三行。你该怎么办?每条线的入选概率是多少?这怎么能一概而论呢?
用概率⅓,用第三行替换我们要输出的结果。
每行的选择概率为:
- 第一行:1 × × ⅔= ⅓
- 第二行:× ⅔= ⅓
- 三线:⅓= ⅓
所以,概率分布是均匀的。我们可以将其概括为AlgoOnePass:
先不说:我(Carl)回忆起一位受访者开始通过归纳法证明这个算法的正确性。我可以告诉他们很容易做到这一点,所以我阻止了他们,继续前进。他们获得了奖金。
奖金 Q: 我们在面试中从来没有问过这个,但是你能递归地写AlgoOnePass吗?
奖金 A: 这里是AlgoOnePassRecurse:
Python 的可选参数可以很好地处理递归。可悲的是,如果递归超过几千次,Python 就会崩溃。这段代码使用包尾递归来避免崩溃。然而,即使如此,这种递归代码的运行速度比迭代代码慢几百倍。
旁白:实际思考一下,一次通过比两次通过重要吗?往往不会。如果你能为一条随机的线等待 20 秒,你大概能等待 20 到 40 秒。另一方面,有些数据——“流数据”——不能被访问两次,所以
AlgoOnePass具有实用价值。
对*AlgoOnePass*的一个简单概括是选择多条随机线,而不仅仅是一条线,我们不会讨论这个。维基百科在其关于储层采样:简单算法 R 的文章中描述了(或多或少)*AlgoOnePass*和这一概括。
在采访中,这通常是我们说到“从文件中随机抽取一行”的地方。然而,我们最近了解到了锈箱[bytecount](https://lib.rs/crates/bytecount)。这个[bytecount](https://lib.rs/crates/bytecount)板条箱使用 SIMD CPU 指令来加速计算文件中的行数。这让我们再次玩起了这个问题。这导致了一种新的方法和一种改进的算法。新算法不使用[bytecount](https://lib.rs/crates/bytecount)。然而,在返回一个随机行的特定情况下,它确实优于维基百科中描述的更一般的 Optimal: Algorithm L 。
旁白:我们称之为“新算法”,但它可能早就被发现了。无论如何,我们希望你会对这个算法和它的发展感兴趣。
和以前一样,我们将通过一系列问题和提示来展示新算法。然而,我们从未向受访者提出过这些问题。
问:在一次传递中,你能从一个未知长度的文本文件中选择一个随机行,使得随机数发生器的调用比行数 n 少得多吗?
答:我们必须澄清“少很多”。我们的意思是对随机数生成器的调用次数小于 O( n )【参见大 O 标注—维基百科】。换句话说,将通话次数减少一次或一半是不够的。所需调用的随机数应该与 log( n )或 sqrt( n )成比例增长。
提示:首先修改AlgoOnePass打印分配给result的每一项的索引。称之为“保持指数”。比如说,对 100 万个项目运行代码。
产出:
1 36 41 187 226 403 2,608 5,756 20,162 48,750 912,566 922,409
这表示当我们以随机种子 0 运行时,第一项(索引 1)被保留为可能的结果。那么没有项目被保留到第 36 个项目,然后是第 41 个项目。如果迭代器包含 922,409 到 1,000,000 项,那么第 922,409 项将是最后一项,因此将被返回。
keep 指数似乎大致呈指数增长。如果该猜想为真,那么 keep 索引数为 O(log n ),其中 n 是迭代器中项的个数。
子问题:我们可以直接随机生成 keep 索引序列吗?
Sub A: 是的!我们在一篇简短的在线技术论文中详细介绍了我们的解决方案[Meek & Kadie,Streaming Random Selection Using the Attenuated Geometric Distribution,2022]。我们称 keep 指数的分布为“衰减几何分布”。我们证明,如果index1是一个 keep 索引号,那么我们可以生成下一个:
r = rng.random()
index1 = index1 + max(ceil(r * index1 / (1.0 — r)),1)
其中rng.random()生成 0 .0(含)到 1.0(不含)之间的统一浮点值。奖金:max(…,1)处理随机生成 0.0 的非常,非常不可能的情况。此外,回想一下我们的约定,即“index1”是从 1 而不是从 0 开始计数的索引。
我们现在可以利用这种洞察力来创建AlgoOnePassSkip:
我们可以通过使用代码在 0(包括)和 100(不包括)之间挑选一个数字,100,000 次,来获得一些信心。
输出图应该看起来一致。他们确实这样做了:

这个算法与 Optimal: Algorithm L (维基百科推荐)有两个重要的区别。
AlgoOnePassSkip只能选择一个随机项,而算法 L 可以选择任意指定数量的随机项。- 当只需要一个随机项时,
AlgoOnePassSkip每个 keep 索引需要一个随机数,而算法 L 需要两个。
因此,对于我们只想要一个随机物品的特殊情况,AlgoOnePassSkip使用的随机抽取数量是算法 l 的一半
摘要
我们已经看到了从未知长度的序列中随机选择一个项目的四种方法。在文本文件的上下文中,第一种解决方案要求文件适合内存。下一个解决方案使用较少的内存,但需要两次通过文件。然后,我们使用概率计算将此减少到一遍。这种一次通过的解决方案需要每行一个随机数。最后一个解决方案需要的随机数比直线少得多。它使用的随机数也是“最优”(更一般)算法的一半。
我们的代码都没有使用系统级的方法,比如[bytecount](https://lib.rs/crates/bytecount)来加速文件的线性传递。添加系统级优化将是一个有趣的扩展。
请 跟随卡尔上中 。我写的是 Rust 和 Python 的科学编程、机器学习和统计学。我倾向于每月写一篇文章。
面试问题:从文件中随机选择一行
一个很酷很有用的算法解释和扩展
卡尔·m·卡迪和克里斯托弗·米克

一只螃蟹从文件中随机选择一行文本——来源:【https://openai.com/dall-e-2/
如果我(卡尔)面试你在微软的工作,我可能会问你这个问题:
如何从未知长度的文本文件中随机选择一行?
我在微软研究院,在最初的反垃圾邮件团队,在一个办公室机器学习/数据科学小组,都问过这个问题。我们喜欢这个问题,因为它涉及概率、算法和系统问题。
即使已经从微软退休,我们仍然会思考随机线问题。例如,我们最近学习了[bytecount](https://lib.rs/crates/bytecount)。这是一个 Rust crate,它使用 SIMD CPU 指令来加速计算文件中的行数。正如我们稍后将描述的,我们对板条箱的了解间接导致了对随机线问题的最先进的解决方案的改进。
我们将这篇文章组织成一系列的问题和提示。如果你愿意,在看到我们的回答之前,你可以试着自己回答问题。
在本文中,我们将在 Rust 中给出答案。关于 Python 中的答案,参见本文的 Python 版。你可能也喜欢阅读这两个版本,作为比较两种语言的一种方式。
对于白板面试,我们给了受访者以下建议:
- 请随意提出澄清性问题。(在本文的上下文中,您可以进一步阅读,看看我们是否提供了澄清。)
- 首先从正确的算法开始,即使它可能不是最佳的。如果我们想要一个更好的算法,我们会用后续问题来提示您。
- 不要担心精确的语法。例如,我们不关心你是否记得产生随机数的确切方法。
- 如果你卡住了,我们可以提供一些提示。(还是那句话,在这篇文章的上下文中,读远一点寻找提示。)
让我们从问题开始:
问:如何从未知长度的文本文件中随机选择一行?
答:我们首先要明确一个“随机线”的含义。我们的意思是文本文件中的每一行都有相等的机会被返回。换句话说,我们希望算法通过均匀分布在这些线中进行选择。
这个要求意味着你不能使用这个算法,我们称之为AlgoRandomSeek:
- 向文件系统询问文件的长度。
- 随机选择一个文件位置并查找到该位置。
- 在位置附近返回一条线。
Sub Q:AlgoRandomSeek怎么了?
Sub A :虽然该算法可以返回文件中的任何行,但它返回较长行的概率比返回较短行的概率高,因此不会通过均匀分布选择行。
旁白:我们喜欢被要求澄清。它将“随机”的日常含义与统计学、数据科学、决策理论和机器学习中使用的技术含义区分开来。
提示:如果你在 Rust 中编码,需要快速(但正确地)解决随机线问题,你会怎么做?
我们向 GitHub Copilot 寻求 Python 的解决方案。把它的答案翻译成 Rust 给了我们这个代码。称之为AlgoReadAll:
旁白:完整的 Rust 项目,包括 Cargo.toml 和
use语句,见 GitHub 上的项目 random-line。所有的例子都使用获取数据箱根据需要获取样本文件。
这个算法显然是正确且快速的。另外,如果遇到坏文件,它会使用?返回一个错误结果。另一个好处是,您可以提到代码在空文件上返回None。
不利的一面是,这段代码将整个文件读入内存,因此不能处理大文件。
接下来,让我们询问一个适用于大型文件的解决方案:
问:使用很少的内存,如何从未知长度的文本文件中随机选择一行?
答:我们首先要澄清“小内存”。对于这个问题,假设您可以存储文件中的任何一行,或者几行,但不是所有的行。
AlgoTwoPass用 1 解决问题。计算文件中的行数,2。随机选择一个行索引,3 .返回带有该索引的行。(在本文中,“索引 0”是一个从 0 开始计数的索引。如果一个索引从 1 开始计数,我们称之为“index1”。)
在我的机器上,这将输出:
1,683 of 146,933: Some(“to gather material for illustrations of the poems of Robert”)
AlgoTwoPass正确。我们还会给这段代码一个奖励:
- 使用带有显式种子的随机数生成器—机器学习和数据科学通常需要再现性,即使是来自随机性。
- 提到它在应用于空文件时返回
None。 - 使用
let item = item_result?检查可能的文件错误。 - [非常小]用千位分隔符格式化数字。
不是必需的,但有趣的是这个更具功能性的实现:
旁白 1: 背景信息:这段代码使用 Rust 迭代器。当*next()*被应用到 Rust 迭代器时,序列中的下一个值被返回到Some中,或者,如果序列是完整的,则返回None。表达式BufReader::new(File::open(&path)?).lines()返回“行结果”上的迭代器。每一行结果要么是一个字符串(下一行),要么是一个错误(在试图读取该行时发现)。虽然 Rust 在迭代器上定义了一个count()方法,但是我们不应该在这里使用它。为什么?因为count()方法不检查错误值。同样,Rust 定义了一个nth()方法,该方法增加了一个迭代器 n +1 个值。它返回( n +1)ᵗʰ值,但忽略任何中间误差值。为了检查来自.line()的错误值,上面的回答定义并使用了try_count和try_nth函数。【感谢 Reddit Rust 社区在这个问题上的帮助。]
旁白 2:我们不会因为某人在*rng.gen_range*或*nth/try_nth*上犯了一个失误而惩罚他。然而,我们可能会问,如何测试代码来检查这样的错误。
接下来,我们要求一个更快的算法:
问:在一遍中,你能从一个未知长度的文本文件中随机选择一行吗?
答:根据提示,我们将通过一系列子问题来开发一个算法。
提示:递归地想想这个。
Sub Q: 把自己放在程序的位置上。如果我们向您保证文件只包含一行,您应该怎么做?
如果文件只包含一行,就输出它。
子问:呜呜,我们骗了你。该文件可能只包含一行或两行,但没有其他数量的行。你该怎么办?
Sub A: 用概率 0.5,替换我们要输出的结果,用第二行。
子 Q: 对不起,我们又说谎了!该文件可能只包含一行、两行或三行。你该怎么办?每条线的入选概率是多少?这怎么能一概而论呢?
用概率⅓,用第三行替换我们要输出的结果。
每行的选择概率为:
- 第一行:1 × × ⅔= ⅓
- 第二行:× ⅔= ⅓
- 三线:⅓= ⅓
所以,概率分布是均匀的。我们可以将此归纳为AlgoOnePass:
说句题外话:我(Carl)回忆起一位受访者开始通过归纳法证明这个算法的正确性。我可以告诉他们很容易做到这一点,所以我阻止了他们,继续前进。他们获得了奖金。
奖金 Q: 我们在面试中从来没有问过这个,但是你能递归地写AlgoOnePass吗?
奖金 A: 这里是AlgoOnePassRecurse:
如果递归超过几千次,Rust 就会崩溃。这段代码使用机箱尾调用来避免崩溃。该算法的递归和非递归版本以大约相同的速度运行。该代码因跨结果迭代器通用而获得了额外的好处。另一个好处是,它接受随机数生成器rng作为&mut,这允许继续使用rng。
旁白:实际思考一下,一次通过比两次通过重要吗?往往不会。如果你能为一条随机的线等 1 秒,你大概能等 1 到 2 秒。另一方面,有些数据——“流数据”——是不能二次访问的,所以
*AlgoOnePass*有实用价值。
对*AlgoOnePass*的一个简单概括是选择多条随机线,而不仅仅是一条线,我们不会讨论这个。维基百科在其关于储层采样:简单算法 R 的文章中描述了(或多或少)*AlgoOnePass*和这种概括。
在采访中,这通常是我们说到“从文件中随机抽取一行”的地方。然而,我们最近了解了锈箱[bytecount](https://lib.rs/crates/bytecount)。这个[bytecount](https://lib.rs/crates/bytecount)机箱使用 SIMD CPU 指令来加速文件中的行数。这让我们再次玩起了这个问题。这导致了一种新的方法和一种改进的算法。新算法不使用[bytecount](https://lib.rs/crates/bytecount)。然而,在返回一个随机行的特定情况下,它确实优于维基百科中描述的更一般的 Optimal: Algorithm L 。
旁白:我们称之为“新算法”,但它可能早就被发现了。无论如何,我们希望你会对这个算法和它的发展感兴趣。
和以前一样,我们将通过一系列问题和提示来展示新算法。然而,我们从未向受访者提出过这些问题。
问:在一次传递中,你能从一个未知长度的文本文件中选择一个随机行,使得随机数发生器的调用比行数 n 少得多吗?
答:我们必须澄清“少很多”。我们的意思是对随机数生成器的调用次数小于 O( n )【参见大 O 标注—维基百科】。换句话说,将通话次数减少一次或一半是不够的。所需调用的随机数应该与 log( n )或 sqrt( n )成比例增长。
提示:首先修改AlgoOnePass打印分配给result的每一项的索引。称之为“保持指数”。比如说,对 100 万个项目运行代码。
产出:
1 2 4 14 38 112 210 914 4,512 5,659 6,242 13,388 917,008
这表示当我们以随机种子 0 运行时,第一项被保留为可能的最终随机项。然后是第二项,然后是第四项。然后,直到第 14 个项目,然后是第 38 个项目,才保留任何项目。如果迭代器包含 917,008 到 1,000,000 个条目,那么第 917,008 个条目将是最后一个被保留的条目,也是最后一个随机条目。
keep 指数似乎大致呈指数增长。如果该猜想为真,则保留索引的数量为 O(log n ),其中 n 是迭代器中项的数量。
子问题:我们可以直接随机生成 keep 索引序列吗?
Sub A: 是的!我们在一篇简短的在线技术论文中详细介绍了我们的解决方案[Meek & Kadie,Streaming Random Selection Using the Attenuated Geometric Distribution,2022]。我们称 keep 指数的分布为“衰减几何分布”。我们证明,如果index1是一个 keep 索引号,那么我们可以用以下公式生成下一个索引号:
let r: f64 = rng.gen(); index1 += ((r * (index1 as f64) / (1.0 — r)).ceil() as usize).max(1);
其中rng.gen()生成 0 .0(含)和 1.0(不含)之间的统一浮点值。额外收获:(…).max(1)处理随机生成 0.0 这种非常非常不可能的情况。此外,回想一下我们的约定,即“index1”是从 1 而不是从 0 开始计数的索引。
我们现在可以利用这种洞察力来创造AlgoOnePassSkip:
我们可以通过使用代码在 0(包括)和 100(不包括)之间挑选一个数字,100,000 次,来获得一些信心。(我们使用表达式(0..100).map(Ok::<_, std::io::Error>)来创建结果值的迭代器,Ok(0),Ok(1),… Ok(99)。代码需要结果值。)
如果我们用 Python 绘图,这些图应该看起来是一致的,事实也确实如此:

该算法与 Optimal: Algorithm L (维基百科推荐)在两个重要方面有所不同。
AlgoOnePassSkip只能选择一个随机项,而算法 L 可以选择任意指定数量的随机项。- 当只需要一个随机项时,
AlgoOnePassSkip每个 keep 索引需要一个随机数,而算法 L 需要两个。
因此,对于我们只想要一个随机物品的特殊情况,AlgoOnePassSkip使用算法 l 一半数量的随机抽取。
摘要
我们已经看到了从未知长度的序列中随机选择一个项目的四种方法。在文本文件的上下文中,第一种解决方案要求文件适合内存。下一个解决方案使用较少的内存,但需要两次通过文件。然后,我们使用概率计算将此减少到一遍。这种一次通过的解决方案需要每行一个随机数。最后一个解决方案需要的随机数比直线少得多。它使用的随机数也是“最优”(更一般)算法的一半。
Rust rand crate 用[seq::IteratorRandom::choose](https://docs.rs/rand/latest/rand/seq/trait.IteratorRandom.html#method.choose)和相关方法实现相关算法和概括。与count和nth一样,它不会检查每个迭代结果中的错误。人们可以想象,在未来,一个try_choose。此外,它使用更通用的算法 L,而不是新的,在某些情况下稍微更有效的AlgoOnePassSkip。
我们的代码都没有使用像[bytecount](https://lib.rs/crates/bytecount)这样的系统级方法来加速文件的线性传递。添加系统级优化将是一个有趣的扩展。
请 跟随卡尔上媒 。我写的是 Rust 和 Python 的科学编程、机器学习和统计学。我倾向于每月写一篇文章。
入门级数据密集型工作的面试问题
原文:https://towardsdatascience.com/interview-questions-for-entry-level-data-intensive-jobs-a6647f2652b8
批判性地思考基本技能和经验

泡菜的食物准备。图片由作者提供。
我在一个应用社会科学项目中教授数据课程,让学生为在社区机构或政府机构中处理各种社会和环境问题做准备。我的学生没有为数据科学的职业生涯做准备。尽管如此,他们仍将担任数据密集型职位,例如,数据分析师、商业智能(BI)经理、项目评估员、研究协调员、质量改进协调员等。
虽然市场对数据技能有很强的需求,但最好的工作竞争非常激烈。这篇文章介绍了我在非营利部门的咨询工作中遇到的面试问题。当我雇用高年级学生和应届毕业生作为分析师或协调员加入我的研究项目时,我也会问这些问题。我为每个问题提供了基本原理,以帮助初级分析师的雇主获得关于申请人的有用信息。不过,更重要的是,这些基本原理可以让学生和分析师在培训中获得对市场需求的重要见解。有了这种认识,学生们可以更加审慎地安排他们的时间。
我从你的简历中看到,你具备使用不同软件应用程序的技能。在什么样的软件环境中,你最擅长处理数据?向我介绍一下你的投资组合,这样我可以更好地了解你的技能水平。
我经常看到申请人提供一系列软件技能。流水账给出了申请人对软件的认知和接触的信息,但不是熟练程度。例如,在我的数据入门课程中,大多数学生都表示他们“了解”Excel。然而,很少有人知道如何创建数据透视表或使用 VLOOKUP 函数。
我发现,大多数申请人表示他们知道并接触过不同的软件应用程序,但并不熟练。熟练程度的最强有力的证明是一些可以共享的数据产品——理想情况下,是作为专业作品集组织的作品集合。不管申请中列出的培训、经验或学位如何,我会筛选掉那些不能提供作品集或工作实例的申请人。
对于入门级的职位,我通常不寻求在给定的软件环境中的高水平的熟练程度。假设申请人在数据原理方面有很强的基础,在给定的软件环境中精通数据工作的申请人可以在不同的软件环境中快速提高技能。
你有许多看起来像是课堂作业的文件夹示例。你做过什么个人项目吗?我很想知道你是否已经独立构思并实施了一个项目。
个人项目是为了独立培养技能和获得经验而开展的活动。课程作业有固定的截止日期、评分标准和内容参数。换句话说,课程教师定义课堂作业的交付内容。我感兴趣的是申请人能在多大程度上做出项目决策。申请人能否独立地构思一个项目,将项目分解成可管理的步骤,迭代不同的解决方案,解决未预料到的问题,并定义项目何时完成?这些类型的决策需要很多微观层面的技能,这些技能需要时间来培养。个人项目是培养这些关键的微观技能的理想方式。
个人项目超出了课程的要求,标志着申请人对参与式学习的承诺。在有技能缺陷或经验不足的申请者中,如果他们有很强的工作组合,特别是个人项目,我仍然会考虑他们的职位。个人项目是内在动机和激情最重要的指标之一。
我经常向学生强调做个人项目的重要性,以积累使用真实世界数据的经验。这里有几篇描述不同方法的文章:
告诉我你是如何与这个领域保持同步的。你关注哪些不同的社交媒体网站和人?你从哪里获取最新发展的信息?
这一系列问题与我对参与式学习的兴趣有关。我想知道申请人从哪里以及如何获得与数据工作直接相关的信息。不同的社交媒体平台可以成为帮助解决问题的绝佳资源。这些问题有助于澄清他们与更广泛的专业领域的关系。我不关心申请人是否有社交媒体,除非他们的信息具有煽动性或分裂性。
说说你管理一个项目的经历。
寻求初级职位的申请人没有太多的项目管理机会。即使没有正式的经验,我也会寻找一些指标来帮助阐明他们对管理活动以实现更广泛的项目可交付性的想法。人们不喜欢在日常工作中被微观管理。作为一名主管或导师,我不想成为一名微观管理者。我的兴趣是创建一个团队,在这个团队中,我们对项目目标和可交付成果有共同的理解。再说一次,个人项目是帮助填补经验空白的绝佳方式。
描述你对项目任务进行优先排序的方法。
这个问题与项目管理有关。但是,我试图理解申请人区分忙碌和高效的能力。处理数据的时候,一整天的工作可以很快过去。然而,记录一整天的工作并不意味着工作对给定的项目有意义的推进。带着这个问题,我想知道申请人用什么系统来监控他们的生产力。申请人能否意识到他们在某项任务上花费了太多时间,并可能需要一些指导?申请人是否认识到在项目截止日期前开始任务的重要性,留出足够的时间解决潜在的意外障碍?时间是最宝贵的资源之一,需要仔细和持续的管理。
我看你技术很强。你认为你的优势在哪个“内容”领域?你有什么工作例子来说明你是如何把你的技术技能和内容领域结合起来的吗?
有效地处理数据需要上下文。拥有内容专业知识有助于理解数据的意义和价值。如果没有上下文,分析师可能会在不清楚什么和为什么重要的情况下盲目地进行分析。申请人的内容区域可能与职位不完全一致。然而,申请人应该能够清楚地说明将技术技能与给定的内容领域相结合的重要性。展示这种理解让我相信申请人能够胜任这个职位。
给我描述一下你解决数据问题的策略。
这个问题对于新分析师来说至关重要。当我问这个问题时,我想了解他们如何处理以前没有遇到的问题。我当然会给新分析师时间来探索不同的解决方案。勘探还必须与效率和项目资源相平衡。申请人能清楚地说明一个有效的方法吗?他们用什么网站?他们有在社交媒体上发布问题的经验吗——如果有,他们知道最佳实践吗?在决定寻求帮助之前,他们会花多长时间解决一个问题?
下面是我写的一篇关于解决这个问题的短文。入门级数据分析师应该了解这些基本策略。
你为什么喜欢和数据打交道?什么让你兴奋?
这一系列问题有助于我了解申请人是否适合这个职位。我想与对高质量工作和学习新内容和技能充满热情的人一起工作。我想知道那个人的激情。
你喜欢打扫卫生和准备数据吗?
当然,我并不期望每项活动都是令人愉快的。但是,对我来说,一个交易破坏者是暗示他们不喜欢数据清理和准备的声明。如果您读过数据科学文献,我相信您会看到评论和辩论,我们将大约 80%的时间和资源用于数据清理和准备。这个估计适用于我所有的项目。不幸的是,许多人认为数据清理和准备是平凡的低级工作。那远非事实。数据的质量决定了数据项目的成功。数据清理和准备涉及高水平的技能和创造力。如果申请人不喜欢这种类型的工作,我相信他们会认真对待并尽最大努力。
后续步骤
数据领域发展迅速,需要入门级的数据专业人员。无论是雇主还是求职者,我提供这些面试问题和理由来帮助你批判性地思考入门级数据密集型职位的面试。请随意使用评论部分来提供您已经问过或被问过的其他面试问题。
进入变压器
原文:https://towardsdatascience.com/into-the-transformer-5ad892e0cee
数据流、参数和维度

约书亚·索蒂诺在 Unsplash 上拍摄的照片
简介:
Transformer 是谷歌研究人员在 2017 年推出的一种神经网络架构,已被证明是自然语言处理(NLP)领域的最先进技术,随后进入了计算机视觉(CV)。
尽管网上有许多解释其体系结构的资源,但我还没有遇到一种资源明确地谈到数据以矩阵形式流经转换器时的更详细的细节。
因此,本文涵盖了转换器中所有子层的维度(输入、输出和权重)。最后,计算出一个示例变压器模型中涉及的参数总数。
对 Transformer 模型的基本熟悉有助于从本文中获益,但并不是必须的。需要进一步解释变压器基础知识的人,可以看看文末提到的参考资料。
本文组织如下:
1。变压器:
变换器由编码器和解码器组成,各重复 N 次(原研重复 6 次),如图 1 所示。

图 1:Transformer——模型架构(来源:注意力是你所需要的)
就机器翻译而言,输入(在编码器端)是来自源语言的单词标记,而输出(在解码器端)是来自目标语言的单词标记。
如图 2 所示,数据从编码器流向解码器、。
每个编码器的输出是下一个编码器的输入。最后一个编码器的输出馈入 N 个解码器中的每一个。除了最后一个编码器的输出,每个解码器还接收前一个解码器的输出作为其输入。

图 2:编码器和解码器层之间的数据流(图片由作者提供)
现在,让我们看看编码器和解码器,看看它们如何通过接受相同维度的输入来产生维度为*Txdm*的输出。这里,注意,馈送到编码器和解码器的输入数量(分别为*TE*和*TD* 、)可以不同,而每个输入的维度(编码器和解码器)保持相同(即*1xdm*)。关于这些尺寸的更多细节将在后面介绍。
这些编码器和解码器层本身包含子层。

图 3:编码器和解码器(作者图片)
2。编码器:
编码器内部有两个子层。
- 多头注意力
- 正向输送
编码器中的多头注意:
多头注意力是 Transformer 架构中至关重要且计算量最大的模块。该模块将*T (=TE*)个大小为*1xdm*的向量作为输入(打包成一个大小为*Txdm*的矩阵),并产生一个大小为*Txdm*的输出矩阵(一包*T*个大小为*1xdm*的向量),如图图 4 所示。


图 4:多头关注(图片由作者提供)
注意头*Head_i*接受来自“输入嵌入+位置编码”的输出(或来自先前编码器的输出)作为输入,并通过将输入乘以相应的权重矩阵来产生*Query*、*Key*和*Value*矩阵。

*q<1>*、*k<1>*、*v<1>*分别是*x<1>*通过投影矩阵*wQ*、*wK*、*wV*、、的投影。类似地,对于位置 2 到 t。
并且两者的尺寸都是*1xdK*而尺寸都是*1xdV*。
矩阵*A’*的元素是每个查询向量*q<>*相对于每个关键向量*k<>*的比例点积。(相同大小的两个向量 a 和 b 的点积为*a.b = abT*,其中*bT*为*b*的转置)。

在矩阵A’的每一行应用 Softmax 得到矩阵A。这就是成比例的点积注意力。

*A*的 row-1 中的元素表示 query-1 对从 1 到 t 的所有键的关注,Row-2 是 query-2 对所有键的关注,以此类推。*A*中的每一行总计为 Softmax 的输出)。
*Head_i*的输出是矩阵*A*和*V*的乘积。

根据矩阵乘法的定义,*Zi*的 row-1 是以*A*的 row-1 的元素为权重,对*V*的所有行进行加权求和。*Zi*的 Row-2 是*V*所有行的加权和,以*A*的 row-2 的元素为权重,以此类推。注意每排*z<>*的尺寸与*v<>*的尺寸相同。并且*Zi*中的行数和*A*中的行数一样多。
现在,所有头的输出被连接起来形成*Z’*。并乘以*W⁰*以产生多头关注子层的最终输出,即*Z*。

Z’中每一行的维数为*1xhdV* ( *h* 个大小为*1xdV*的向量串接而成)。矩阵*W⁰*的维度为*hdVxdm*,将每一行Z’从*1xhdV*维度投影到*1xdm*。
因此,编码器中的多头关注子层接受大小为*Txdm*的输入(每个*T*个输入的数量为*1xdm*个),并产生相同大小的输出*Txdm*(每个*T*个输出的数量为*1xdm*)。这也被称为输入-输入注意或编码器自我注意,即输入句子的每个位置注意输入句子本身的所有其他位置。
编码器中的前馈网络:
前馈网络接受大小为*Txdm*的输入(每个*T*个输入的数量为*1xdm*个),并执行以下功能以产生相同大小的输出*Txdm*。这里,*T=TE*。

该网络执行两次线性变换(通过*W1*和*W2*),其间具有 ReLU 非线性。*W1*将尺寸*1xdm*的每个输入转换成尺寸*1xdff*,并将*1xdff*转换回另一个*1xdm*尺寸。
因此,前馈子层产生与输入维数相同的输出,即*Txdm*。
3.解码器:
解码器内部有三个子层。
- 掩蔽的多头注意力
- 多头注意力
- 正向输送
解码器中屏蔽的多头注意力:
解码器中的掩蔽多头注意力也称为输出-输出注意力或解码器自我注意力。该模块将*T (=TD*)个大小为*1xdm*的向量作为输入,并产生一个大小为*Txdm*的输出矩阵*Z*。这与编码器中的多头注意力子层(参见图 4)相同,除了一个变化——遮罩。此掩码防止查询位置关注未来位置的键,从而保留自回归属性。因此,允许查询*q<t>*只关注从*k<1>*到*k<t>*的键。这是通过在*A’*中将禁止的查询键组合的位置设置为*-infinity*来实现的。

因此,解码器中被屏蔽的多头注意力子层接受大小为*Txdm*(每个*T*个输入的数量为*1xdm*个)的输入,并产生相同大小*Txdm* (每个*T*个输入的数量为*1xdm*个)的输出。
解码器中的多头注意力:
解码器中的多头注意力也称为输入-输出注意力或编码器-解码器注意力。这与编码器中的多头注意力子层(参见图 4)相同,只是它从编码器堆栈接收一个额外的输入(称之为X*E*)。该额外输入(大小为*TExdm*)用于产生K和V,而解码器侧的输入(大小为*TDxdm*)用于产生Q。

相应的,*A’*和A的尺寸也会变成*TDxTE*。这表示来自解码器侧的*TD*个令牌中的每一个对来自编码器侧的*TE*个令牌中的每一个的关注。

*Head_i*的输出尺寸为*TDxdV* 。

解码器中的前馈网络:
解码器中的前馈网络与编码器中的前馈网络相同。这里,*T=TD*。
4。外围区块:
变压器模型中的其他外围模块是输入嵌入、输出嵌入、线性、和 Softmax 模块。输入嵌入(输出嵌入)将输入标记(输出标记)转换成模型维度的向量*1xdm*。输入和输出标记是来自输入和输出字典的一键编码。
线性和 Softmax 模块从最后一个解码器获取尺寸为*1xdm*的输入,并将其转换为等于输出字典的一键编码的尺寸。这个输出代表概率分布。
位置编码既不包含任何可学习的参数,也不通过增加嵌入来改变尺寸。因此,不再进一步解释。
5.总结:
具有重复 6 次的编码器-解码器并且在每个子层中具有 8 个注意头的变换器模型具有以下参数矩阵。

图 5:总参数矩阵(图片由作者提供)
对于具有 N 个编码器-解码器层和*h* 注意力头的模型,概括上述内容:
- 每个编码器的参数矩阵数量(MHA+FFN) =
*3h+1 + 4 = 3h+5* - 每个解码器的参数矩阵数量(MMHA+MHA+FFN) =
*3h+1 + 3h+1 + 4 = 6h+6* - 单个编码器-解码器对的参数矩阵的数量=
*3h+5 + 6h+6 = 9h+11* - 模型的参数矩阵总数(NxEnc-Dec+Linear+I . Emb+O . Emb)=
*N(9h+11) + 3*
考虑到前面给出的所有参数矩阵的维数,模型的参数总数如下:
- 每个编码器的参数数量:
*MHA: (dmxdK + dmxdK + dmxdV)h + hdVxdm ----- (1)*
*FFN: dmxdff + 1xdff + dffxdm + 1xdm ----- (2)*
- 每个解码器的参数数量:
*MMHA: (dmxdK + dmxdK + dmxdV)h + hdVxdm ----- (3)*
*MHA: (dmxdK + dmxdK + dmxdV)h + hdVxdm ----- (4)*
*FFN: dmxdff + 1xdff + dffxdm + 1xdm ----- (5)*
- 外围模块中的参数数量:
*Linear + I.Emb + O.Emb: I_dictxdm + O_dictxdm + dmxO_dict -- (6)*
这里,*I_dict* 是机器翻译中的输入语言词典大小,*O_dict* 是输出语言词典大小。
- 模型的参数总数=
*N[(1)+(2)+(3)+(4)+(5)]+(6)*
原变压器研究论文中提到的基础模型(注意是你所需要的)使用尺寸*dm = 512*、*dK = 64*、*dV = 64*、*dff = 2048*、*h = 8*、*N = 6*,共有*65 million*个参数。
结论:
转换器主要用于构建语言模型,帮助执行各种 NLP 任务,如机器翻译、自动摘要、对话管理、文本到图像的生成等。
几个拥有数十亿参数的大型语言模型,包括最近轰动一时的 ChatGPT,展示了非凡的对话能力,都以变形金刚为构建模块。
我希望通过研究这个构件,您能够理解这些大型语言模型最终是如何包含数十亿个参数的。
参考资料:
1 Jay Alammar,图文并茂的变形金刚博文,2018。
[2]瓦斯瓦尼等人,注意力是你所需要的全部,2017。
神经网络的有趣特性
原文:https://towardsdatascience.com/intriguing-properties-of-neural-networks-57fef936adc7
神经网络是如何工作的?

莫里茨·金德勒在 Unsplash 上拍摄的照片
十多年来,神经网络一直是机器学习算法中的佼佼者。他们在不同领域的众多任务中创造了奇迹。然而,关于它们实际上是如何工作的,一直存在疑问。
神经网络可以拟合任何训练数据。它试图通过调整分配给每个输入的权重和随后的中间步骤(隐藏层)来学习输入和输出之间的映射。这种调整是通过监督学习的反向传播自动完成的。因此,很难解释这些权重是如何对映射做出贡献的,并且它还表现出一些反直觉的特性。
T wo 这些特性在我们将在本文中讨论的'神经网络的有趣特性中有所涉及。虽然它可以追溯到 2014 年,但它仍然是相关的,并丰富了 w.r.t .对神经网络工作的理解。
以下是所述属性:
- 与经验结果相反,是而不是单个神经元(单元)而是整个向量对输入的语义信息进行编码。
- 将不易察觉的、非随机 噪声添加到输入中,甚至可以使最先进的深度学习模型对它们进行错误分类,证明该模型对小扰动敏感,并对它们的稳定性提出质疑。
这听起来可能有点模糊和混乱,但是我们将在接下来的章节中更详细地探讨这些。
内容
- 符号
- 术语
- 神经元 v/s 神经元
- 神经网络的盲点
→实验结果
→不稳定性分析 - 结论
- 参考文献
符号
**x ∈ ℝᵐ** is an input image (a vector of real numbers of length m).***I***is the test-set of images.**𝜙(x)** denotesactivations for an entire layer for the given image.**𝜙₁(x), 𝜙₂(x), 𝜙₃(x), ...** denotes activations of individual neurons.**φ(x)** denotes the output of an entire network of **K** layers.**φ*₁*(x; *W₁*), ..., φ*ₖ*(x; *Wₖ*)** denotes the outputs of individual layers.**φ(x) = φₖ ( φₖ₋₁ (... φ****₁** **( x; *W₁* ); *W₂* ) ...; *Wₖ* )****ρ(x)** ReLU, i.e., **max(0, x)****σ₁** Largest singular value of a matrix
术语
编号对应于正文中引用这些术语的上标。
- 激活:一个神经元(或一整层)的最终输出,包括激活函数。
- 单位:单个神经元。
- 基:向量空间中的一组向量
**B**,使得该空间中的每一个向量都可以唯一地表示为**B**中向量的一个线性组合。例如,在三维欧几里德向量空间中,向量**{*î, ĵ, k̂*}**是基向量,因为该空间中的所有向量都可以表示为线性组合:***xî + yĵ + zk̂***。这些也是欧氏向量空间的标准基。标准基是一组向量,它们的分量都是zero,除了一个等于1,即***ĵ = (0, 1, 0)***。 - 方向:在这个上下文中是指随机向量
**𝑣**会指向某个方向,与这个向量的最大点积会给我们指向**𝑣**方向最多的向量。现在,因为据说共享相似特征的图像具有相似的点积值,所以他们说“方向对该特征是敏感的。” - 非局部泛化 : 神经网络的深层隐藏层本质上是一个对空间进行建模的函数,使得相似的输入更接近于这个潜在空间(例如,具有相似特征的同一类的图像)。神经网络将很好地概括以正确预测附近的输入类别。这叫做局部泛化。然而,确实存在一些输入,这些输入可能属于相同的类别,但是由于某些特征使得它们与其他输入不太相似而远离空间中的其他输入(例如,狗的图像可能在附近,但是狗的侧视图可能在空间中远离,尽管它仍然是狗)。神经网络被认为也能够推广这些远距离的输入。这叫做非局部泛化。
- 框式约束优化:这是一个唯一的约束是变量上下限(可以是
**±∞**)的问题。看到这里。
神经元 v/s 神经元
过去的研究表明,特定的神经元对特定的特征做出反应。他们表明,单个神经元的激活对输入具有某种语义意义。为此,他们在图像中寻找相似之处,使同一神经元的激活值最大化:

证明过去研究有效性的 MNIST 实验— Szegedy 等人
例如,如果你想检查某个***iᵗʰ***神经元的激活**𝜙*ᵢ*(x)**,
首先,您可以在测试图像***I*** 集合中找到**𝜙*ᵢ*(x)** 值最大的图像,使用:

使用单个神经元的激活来寻找语义相关的图像— 赛格迪等人
基本上,您可以找到所有***I*** 的**𝜙*ᵢ*(x)** 的最大值,然后挑选值接近此最大值的图像。
注意 作者使用了
***⟨*𝜙*(x),* 𝑒*ᵢ⟩***的表达方式为激活***𝜙*ᵢ*(x)**。这里,**𝑒*ᵢ***是带有**iᵗʰ**值***1***的一键编码(基)向量。和尖括号***⟨⟩***分别代表**𝜙*(x)*****和**𝑒*ᵢ***的点积。所以我们最终做的是:***

⟨𝜙(x),𝑒ᵢ⟩ = 𝜙ᵢ(x)
****然后,你可以目视检查这些图像,找出它们之间的相似之处(比如图像有一条对角线的直线)。
相反,基于他们的观察,作者表明,只有特定神经元对特定特征做出反应的结论是不正确的。
这就对神经网络将坐标间的变异因素分开的概念提出了质疑。
— 塞格迪等人
他们认为整个激活空间包含了大量的语义信息。为了证明这一点,他们采用了一个随机向量 **𝑣** ,而不是一键(标准基)向量**𝑒**。即

使用整个层的激活找到语义相关的图像— Szegedy 等人
在哪里,

所以在这里,不是只有一个**𝜙*ᵢ*(x)**,他们考虑所有**𝜙*ᵢ*(x)** 的一个线性组合,并挑选点积值接近最大值的图像。
简而言之,他们证明了给予一层中所有神经元高值的图像在语义特征上也有相似之处。这表明似乎没有对特定特征作出反应的优选神经元或神经元集,但该层作为一个整体编码了大量语义信息:

MNIST 实验证明层作为一个整体编码语义信息— Szegedy 等人
如果你仍然困惑,想想如果只有特定的神经元对特定的特征做出反应,Dropout会如何工作。
另外,请注意经验主义的说法并没有错。有些神经元确实对某些特征比其他的更敏感。但是这些结果并不比用整个层获得的结果更好,因此,可以说神经网络没有在不同的神经元之间分配不同的特征。

ImageNet 上的实验。左:演示编码信息的单个神经元;右图:展示整个图层编码信息— Szegedy 等人
神经网络中的盲点
研究表明,神经网络的输入和输出层之间的隐藏层的深度堆栈能够在输入空间上实现非局部推广** ⁵。因此,这意味着本地 generalization⁵ 应该按预期工作。简而言之,对于足够小的半径**ϵ > 0**,在给定输入**x**附近,满足**‖r‖ < ϵ**的输入**x + r**将预测正确的类别。**
该论文的作者认为这一假设不成立,他们通过生成具有难以察觉的小扰动的对立样本来证明这一点,这些小扰动使模型误预测类别。
考虑将输入图像**x ∈ ℝᵐ**映射到一组标签**{1 . . . *k*}** — ***f* : ℝᵐ → {1 . . . *k*}**的神经网络分类器***f*** 。***f***具有由***lossf* : ℝᵐ × {1 . . . *k*} → ℝ⁺**表示的损失函数。
现在,我们想要找到给出不正确预测***l***的最接近**x**的图像。设此像为**x + r**。所以为了让**x + r** 最接近**x**,我们需要**r**越小越好。因此,我们可以构造一个框约束优化** ⁶问题:**

对立范例生成问题的形式定义— Szegedy 等
有可能**r**不是唯一的。因此我们将由失真函数***D(x, l)***选择的任意**r** 表示为**x + r**。***D(x, l)*** 的精确计算是一个难题。因此,作者使用框约束 L-BFGS 近似这一点。
具体地说,我们通过执行线搜索来找到下面问题的极小值
**r**满足**f(x + r) = l**的最小值**c > 0**,从而找到**D(x, l)**的近似值。

近似“硬”优化问题— Szegedy 等人
不幸的是,这篇论文没有详细说明他们的具体方法。因此,很难围绕这一点获得更多的直觉。
L-BFGS 和线搜索超出了本文的范围;我将在以后讨论这些内容。这里有几个解释【BFGS】和线搜索的好资源。

为 AlexNet 生成的对抗性示例。左:原图;中心:添加小扰动(生成图像与原始图像之间的差异,放大);右:被错误分类的反例——Szegedy 等人

为 QuocNet 生成的对抗性示例。左:原图;中心:被错误分类的反例;右:添加了小扰动(生成图像和原始图像之间的差异,放大)——塞格迪等人
实验结果
有人可能会认为这些错误分类是由于在一组固定的输入上过度拟合模型造成的。为了证明事实并非如此,作者们提出了一些概括实验:
- ****跨模型推广:与用于生成对立样本的模型相比,使用不同的超参数(如层数、正则化或初始权重)从零开始训练的模型会对图像产生明显的错误分类。

第一列表示通过添加最小扰动生成对立示例的模型,使得测试集中的所有示例都被错误分类。最后一列显示了实现误预测所需的原始训练集的平均失真。这些例子被提供给不同的模型(跨列提及),相应的诱发误差列于上表中— Szegedy 等人
- ****交叉训练集泛化:在与用于生成对抗样本的模型完全不同的训练集上训练的模型,会对图像产生明显的错误分类。

用于交叉训练集泛化的原始模型训练细节。MNIST 样本分为两部分——P1 和 P2。最后一列指示测试集中所需的平均最小失真,使得该集中的所有图像被相应的模型错误分类— Szegedy 等人

针对一个模型生成的对立示例的错误率,并将其提供给在不同训练集上训练的另一个模型(见上表)——Szegedy 等人
不稳定性分析
从上一节中,我们了解到,尽管已知神经网络可以在局部和非局部点上进行推广,但如果添加正确,它仍然对微小的扰动高度敏感。这就提出了一个关于它们稳定性的问题,也就是说,如果输入的微小变化改变了模型的预测,以至于它变得不正确,那么模型可能根本就不稳定。
为了研究这一点,我们将使用 Lipschitz 连续性的概念 :

对于 Lipschitz 连续函数,我们可以画一个双锥(白色区域),这样如果我们沿着函数移动它的原点,函数就不会进入白色区域— 维基百科
Lipschitz 连续函数的变化速度有限。对于 Lipschitz 连续函数,连接该函数上任意两点的直线的斜率大于一个实数。这个实数称为李普希茨常数:

这确实非常直观。例如,斜率给出了***f(x)*** 随***x***变化多少的度量。因此,我们可以说,如果斜率以一个小值为界,那么***x***中的“小扰动”不会对***f(x)***产生大的影响。
现在考虑两个函数***f***和***g***,设***h***为***f***和***g***的组合,即***h = f ∘ g***。为此,

因此,对于***n***功能的组合,即***h = f₁ ∘ f₂ ∘ ... ∘ fₙ***,

同样,在我们的例子中,对于一个***kᵗʰ***层,

其中***Lₖ***是***kᵗʰ***层的 Lipschitz 常数。所以对于整个**K**层的网络,

接下来,我们想找到我们的神经网络的李普希茨常数。给定上面的表达式,可以有把握地说,我们可以为每一层找到***L***,然后取乘积。但是首先,我们需要弄清楚如何找到所有层的***L***:
- 为 ReLU (
**ρ**) :
要找到***Lᵣₑₗᵤ***,我们需要以下内容:
**Given any differentiable function ***f* : ℝ → ℝ** and points **x, y ∈ ℝ**, if the maximum derivative of ***f***, ***dₘₐₓ* = max{∣*f'*(z)∣ | z ∈ ℝ}** exists, then**∣*f*(x) - *f*(y)∣ ≤ *dₘₐₓ*∣x - y∣**So, ***dₘₐₓ*** is the Lipschitz constant of ***f***. For proof, refer [this link](https://www.duo.uio.no/bitstream/handle/10852/69487/master_mathialo.pdf?sequence=1) (Page 48).**
现在,ReLU 由***f*(a) = max(0, a)**定义,即它可以取 2 个值:**{0, a}**。对这个 w.r.t. **a**求导我们得到— ***f’*(a)**可以取 2 个值:**{0, 1}**。所以***dₘₐₓ***是**1**,由此,***Lᵣₑₗᵤ* = 1**。因此,

- 同样,我们可以证明对于 max-pooling 层
**φ(x)**,

- 为 密集层:
连续性由下式给出

由上可知,李普希兹常数是稠密层**‖*Wₖ*‖**的算子范数。可以证明这个范数是以***Wₖ***的最大奇异值为界的。你可以在这里找到证明(第 44 页)。
如果你不明白,回想一下对于***h = f ∘ g*** , 李普希兹常数的值是***f***和***g***的李普希兹常数的乘积。所以在这里,我们可以把**ρ**乘以它的***Lᵣₑₗᵤ*** 值,也就是**1**。
- 类似地,对于对比度归一化层**
**φ(x)**,**

- 对于卷积层:
就我个人而言,我并不觉得这篇论文对此的解释特别直观。根据我的理解,他们已经使用了 Toeplitz 矩阵理论及其与傅立叶分析的联系来寻找卷积层的算子范数的上界。
然而,这只是一篇关于神经网络和不稳定性的介绍性文章,所以我们不会在卷积理论上纠缠太深。相反,我们将研究卷积层的不同解释,我在这里遇到了(从第 44 页开始)。
首先,我们现在来看看卷积运算实际上是如何实现的。
虽然在图像上滑动内核是直观的,但我们的 python 库在执行矩阵乘法时更加舒适和高效。因此,他们不是滑动内核,而是"将图像展开成平坦的张量,用平坦的内核做矩阵乘法,并将其"折叠"到所需的输出维度。

展开和折叠操作
****展开操作例如,如果一幅图像有 3 个通道,那么展开矩阵的行数将是3 x kernel size。展开图像中的列数就是内核幻灯片的数量。另外,请注意展开操作包括相邻幻灯片中的重复像素。
相反,折叠操作** **F(a)**采用展开的矩阵,并在给定内核和输出尺寸的情况下构建图像。**
所以卷积层可以概括为:
**(w ★ a) = F(WᵀU(a))** 现在我们知道了矩阵乘法运算的 Lipschitz 常数。让我们试着找到展开和折叠的方法。
对于足够小的内核(**k x k**)和相对较大的图像,展开操作符最多可以重复像素**k²**次。除此之外,它只是重塑了图像矩阵。折叠运算符也是如此,只是它最多在**k²**次合并一个给定的像素。因此,我们可以说,不管输入的变化如何,展开/折叠操作的变化都受到恒定内核大小**k²**的限制。
让我们来证明这一点:
我们用**1**通道创建一个任意的**4 x 4**张量(图像)。首先,我们打开它,然后折叠它。在这样做的时候,我们检查两个操作的导数。展开和折叠的***dₘₐₓ***为**k²**。因此,**U(a)**和**F(a)** 的李普希兹常数受**k²**限制。
最后,卷积层的连续性由下式给出:

我们已经看到,算子范数**‖*Wₖ*‖** 以核矩阵**σ₁**的最大奇异值为界。
****注意虽然卷积层的这种解释的想法取自这篇论文的文章,但它没有考虑 matmul 之后的折叠运算符。它也没有解释重复操作是如何给出展开的 Lipschitz 常数的。为了提供更好的直觉,我试图填补这些空白。我很乐意看到和解决任何关于相同的评论。

来自 Szegedy 等人的网络各层的界限
可以清楚地看到,所有层的上限值都相当高,表明它们不稳定。
结论
在这篇文章中,我们浏览了 Szegedy 等人的,并试图对这篇论文涉及的神经网络的各种属性有一个直观的了解。
我们发现神经网络在归纳方面可能并不稳定。显而易见,我们应该考虑如何让它们稳定下来。这是一个独立的研究分支,致力于通过正则化各层来推广神经网络,使其成为收缩(基本上是**0 ≤ *Lₖ* < 1**的连续性)。
我们将在未来尝试触及一些这方面的研究。
参考
**https://arxiv.org/abs/1312.6199 https://stats.stackexchange.com/questions/371564/intriguing-properties-of-neural-networks
https://www . duo . uio . no/bitstream/handle/10852/69487/master _ mathialo . pdf
https://en.wikipedia.org/wiki/Lipschitz_continuity https://www.youtube.com/watch?v=zVDDITt4XEA
https://math . Berkeley . edu/~ hutching/teach/54-2017/SVD-notes . pdf
https://yeephycho.github.io/2016/08/03/normalizations_in_neural_networks/ https://arxiv.org/abs/2006.08391 **
3D 深度学习简介
原文:https://towardsdatascience.com/intro-to-3d-deep-learning-e992f7efa6ee
3D 数据表示、视觉任务和学习资源
作者:玛格丽特·梅纳德·里德和 T2
3D 深度学习是一个有趣的领域,具有广泛的现实应用:艺术和设计,自动驾驶汽车,体育,农业,生物,机器人,虚拟现实和增强现实。这篇博客文章介绍了 3D 深度学习:3D 数据表示,计算机视觉任务和学习资源。

作者图片(玛格丽特)
3D 数据
数据对于训练机器学习模型来说超级重要。2D 和 3D 深度学习最大的区别之一是数据表示格式。
常规图像通常以 1D 或 2D 阵列表示。另一方面,3D 图像可以有不同的表示格式,这里有一些最流行的格式:多视图、体积、点云、网格和体积显示。让我们来看看用图片说明的每个数据表示。
多视图图像
这些可以通过定位从同一物体或场景的不同角度拍摄照片的多个相机来捕捉。这是一把椅子的样子,图片来自 ShapeNet ,这是一个注释丰富的大型形状库,由物体的 3D CAD 模型表示。

ShapeNet 数据集的图像
点云
在点云数据集中,每个图像由一组从原始传感器收集的点(x,y,z 坐标)表示。点云数据通常由激光雷达传感器捕获或从网格数据转换而来。
这是来自 ModelNet10 数据集的点云表示中椅子的样子。

来自 ModelNet 10 数据集的图像
网状物
网格是使用 Blender、Autodesk Maya 或 Unreal Engine 等软件进行 3D 建模的典型构件。与点云中每个 3D 对象由点组成不同,网格表示由一组点以及这些点(边和面的关系组成。一种网格是多边形网格,其面为三角形或四边形。

作者图片(玛格丽特)
体积显示
在体积表示中,每个图像都是实心的,由体素组成:2D 图像中像素的 3D 等价物。
诸如 Blender 之类的 3D 建模软件可以用来对 3D 模型进行体素化,这里是一个体素化兔子的例子:

作者图片(玛格丽特)
体积表示可以通过实时扫描获得,或者从 3D 点云或网格转换而来。这里有一个来自scan-net.org的例子,它用语义体素标签对室内场景进行了 RGB-D 扫描。

图片来自 scan-net.org/
3D 计算机视觉任务
就像 2D 计算机视觉一样,3D 任务包括图像分类、分割、姿态估计和使用生成模型的图像合成。下面我们将讨论这些任务的几个例子。由于 3D 数据有如此多不同的表现形式,请注意,我们下面提到的例子可能只涉及一些 3D 数据格式。
三维图像分类
图像分类在 2D 和三维计算机视觉中都是一个很好解决的问题。
3D 数据分类涉及识别场景中存在的单个 3D 或多个 3D 对象的任务。它将使我们能够通过捕捉物体的形状、大小、方向等来识别物体。这在处理增强现实(AR)、自动驾驶汽车和机器人等现实应用时至关重要。
该任务类似于 2D 图像分类,不同之处在于模型架构。 VoxNet (2015)是利用 3D CNNs 进行单个 3D 对象检测的最初作品之一。它接受 3D 体积数据或 2D 帧序列作为输入,并应用 3D 内核进行卷积运算。3D CNNs 是学习体数据表示的强大模型。最近的工作,如 SampleNet (2020)介绍了采样点云的技术,其中包括代表视觉场景的点,从而提高分类性能以及其他任务,如 3D 重建。
这里有一个 Keras.io 上学习 3D 图像分类的很棒的教程:用 PointNet 进行点云分类。
3D 对象检测和跟踪
3D 中的对象检测和跟踪类似于 2D 中的任务,但是具有额外的挑战。3D 对象检测任务我们处理体素或点。
3D 物体检测和跟踪在自动驾驶汽车中非常有用。我们可以使用 RGB 图像、点云数据或来自相机和传感器(激光雷达)点云的融合数据输入来训练 3D 对象检测和跟踪。
对象检测和跟踪也可以用于增强现实,以将虚拟项目叠加到现实世界场景中。

图片来自论文:单目准密集三维物体跟踪
三维图像分割
与 2D 图像分割任务一样,3D 图像分割也包括语义、实例和部分分割。

图片来自纸张:PID-Net 零件分割
根据 3D 数据表示,不同的技术被用于分割任务。用于分割的一些流行的 3D 数据集包括 ScanNet、ShapeNet 和 Semantic3D。
3D 分割的一些应用包括使用无人机进行场景分析、3D 地图重建和医疗诊断。有趣的是,语义分割也有助于深度估计。
三维姿态估计
3D 姿态估计是涉及从作为输入给出的 2D 图像预测 3D 对象的实际空间定位的过程。一旦我们获得 2D 图像中物体的三维旋转和平移等信息,我们就可以将它转换到三维空间。这个问题在机器人领域非常活跃。一个机器人也许能够用照相机看到各种各样的物体,但是仅仅看到一个物体并不足以真正抓住它。
为了解决这个问题,通常为每个对象检测、识别和跟踪许多被称为关键点的重要特征。解决这个问题的早期工作之一是由 Shubham Tulsiani 等人 (2015)完成的,他们引入了一种基于 CNN 的方法,用于从 2D 图像中可靠地预测对象的视点和关键点。

(左)2D 汽车图像(中)使用视点(右)关键点位置表示 2D 汽车的姿态来自纸张的图像:视点和关键点
3D 姿态估计的其他重要应用包括增强现实和时尚虚拟试穿。
三维图像重建
3D 图像重建涉及从关键点、分割、深度图和表示 3D 模型知识的其他形式的数据中理解图像的 3D 结构和方向的任务。随着数据的丰富,基于深度学习的技术在解决这个问题方面也很受欢迎。这些作品基于不同的模型,如 CNN,RNNs,Transformers,VAEs 和 GANs。
多视图重建
这是一项使用代表场景的 2D 图像集合来重建物体的 3D 视图的任务。基于这种技术的深度学习模型从图像中提取有用的信息,并探索不同视图之间的关系。
单视图重建
在单视图重建中,使用单个 2D 图像来完成对象的 3D 视图。这是一项复杂得多的任务,需要模型仅从表示物体单个视图的图像中推断几何结构和视觉特征,如纹理和阴影。然而,该领域已经有许多研究,例如 GAN2Shape 、 PHORUM 已经证明成功地生成了具有精确颜色、纹理和阴影表示的真实感 3D 结构。
用 NeRF 进行三维重建
NeRF 使用单个连续的 5D 坐标作为输入来探索 3D 重建的任务。坐标表示空间位置和观察方向。它输出给定位置的体积密度和视图相关的 RGB 颜色。这最小化了在渲染先前用于 3d 重建任务的多个图像时引入的误差。
这个概念是论文引入的:将场景表示为神经辐射场进行视图合成【Project】【Paper】【Code】。这里还有一个关于 https://keras.io/examples/vision/nerf/的很棒的教程。
学习资源
我们想分享一些帮助我们学习 3D 深度学习的学习资源。
Keras.io 有几个上面提到的 3D 深度学习教程。
如何表现 3D 数据是一篇关于 3D 数据表现的很好的帖子,有更多的细节。加州大学圣地亚哥分校 SU 实验室的 3D 深度学习教程提供了 3D 深度学习的一个很好的概述。GitHub repo 3D 机器学习收集了 3D 数据集、模型和论文等。
有两个 3D 深度学习库: TensorFlow 3D 和 PyTorch3D 。这篇博文用 TensorFlow 3D 理解 3D 场景详细介绍了 TensorFlow 3D 模型。而这些优秀的 PyTorch3D 教程都有 Colab 笔记本,你可以亲自动手探索。
摘要
这篇文章提供了 3D 深度学习的概述:基本术语,3D 数据表示和各种 3D 计算机视觉任务。我们已经分享了一些学习资源,您可能会发现这些资源对开始 3D 深度学习有所帮助。
关于作者——玛格丽特·梅纳德·里德是一名 ML 工程师、艺术家和有抱负的 3D 时装设计师。Nived PA 是阿姆里塔大学计算机工程专业的本科生。
Neo4j 简介:图形数据库
原文:https://towardsdatascience.com/intro-to-neo4j-a-graph-database-19958be6c52f
了解图形数据库 Neo4j 并编写练习查询

作者图片
Neo4j 是什么?
Neo4j 是一个图形数据库,以节点和关系的形式存储数据。这不同于更传统的关系数据库,后者将数据存储为表格。
使用 Neo4j 的好处是,它使用本机图形存储、针对速度优化的可扩展架构,并且它是 ACID 兼容的,这有助于保持基于关系的查询的可预测性。与 MySQL 相比,Neo4j 在图形遍历方面要快得多。
设置
要下载 Neo4j,请点击此处,并按照下载说明进行操作。请务必下载社区版。
下载后,打开应用程序,它将看起来像下面的图片。电影数据库是预加载的,可以用来练习查询。对于本教程,单击旁边的添加数据库按钮,然后按创建本地数据库。然后,将数据库的名称改为 Practice,设置任意密码,并按 create。最后,按 start 启动数据库,然后按 open。

作者图片
什么是节点?
节点是 Neo4j 中表示实体的结构类型。Neo4j 中的每个节点都有一个惟一的 ID。节点也有标签和属性图。Neo4j 中的节点用括号()表示。下面的例子将讨论各种类型的节点以及如何创建它们。
空括号用于创建空节点。运行下面的语句在 Neo4j 中创建一个空节点。
CREATE ()
为了确保已经创建了节点,将弹出如下所示的消息。

作者图片
带有任何字母数字值的括号也表示一个空节点。字母数字值用作空节点的变量。变量只能在一条语句中使用,在其他语句中没有意义。
CREATE (a)
在变量后添加冒号允许添加节点标签。这可以被认为是节点的类型。与 SQL 相比,具有相同标签的所有节点可以被认为是同一个表的一部分。此外,在图形中,具有相同标签的所有节点将具有相同的颜色。
CREATE(a:Person)
该命令运行后将能够看到当前的可视化图形。单击左侧边栏的顶部图标(在下图中以绿色显示)。然后点击*(3)按钮,下面用红色标出,显示整个图表。点击旁边的 PEOPLE 按钮将显示所有带有 PEOPLE 标签的节点。

作者图片
定义节点后,可以以键-值对的形式添加属性。在此示例中,节点的属性是 name,对于此节点,名称是 John,Doe。
CREATE (a:PEOPLE{name:”John, Doe”})
可以在每个节点的花括号内添加多个属性。要查看每个节点的属性,将鼠标悬停在它上面,右侧会弹出一个侧边栏,如下图所示。
CREATE (a:PEOPLE{name:”John, Doe”, age:21})

作者图片
什么是关系?
关系用于连接成对的节点。有两种关系,直接的和直接的。无向关系用 2 条虚线表示。有向节点用箭头→或←表示。
无向关系用于匹配查询,不能用于 create 语句。当关系的方向不重要时,它们用于查找节点之间的关系。
对于有向关系,它们可以简单到只使用箭头来连接节点。
CREATE (a:PEOPLE{name:”John, Doe”}) →(b:PEOPLE{name:”Sally, Mae”})
或者
CREATE (a:PEOPLE{name:”John, Doe”})←(b:PEOPLE{name:”Sally, Mae”})
可以将括号添加到关系中以包括细节。在下面的例子中,变量[p]的用法与节点中的变量相同。
CREATE (a:PEOPLE{name:”John, Doe”})-[p]->(b:PEOPLE{name:”Sally, Mae”})
可以将标签添加到与节点标签相似的关系中。下面的关系显示了通过父关系相关的节点“人员”。无名氏是莎莉·梅的父母。
CREATE (a:PEOPLE{name:”John, Doe”})-[p:PARENT]->(b:PEOPLE{name:”Sally, Mae”})

以上查询的输出(图片由作者提供)
属性可以添加到关系中。这些等同于节点中的属性。在本例中,它显示 John Doe 是 Sally Mae 的父亲。要查看某个关系的属性,请单击该关系并查看右侧的侧边栏。
CREATE (a:PEOPLE{name:'John, Doe'})-[p:PARENT{type:'Father'}]->(b:PEOPLE{name:'Sally, Mae'})

作者图片
一条语句可以创建多个关系。
CREATE (a:PEOPLE{name:'Jane, Doe'})-[p:PARENT{type:'Mother'}]->(b:PEOPLE{name:'Freddy, Mae'})-[g:PARENT{type:'Father'}]-> (c:PEOPLE{name: 'Jill, Smith'})
上面的关系是用 CREATE 语句创建的,因为节点还不存在,所以需要同时创建节点和关系。如果节点已经存在,那么需要使用匹配语句。
比赛声明
匹配语句用于搜索模式。第一个用途是在现有节点之间创建关系。在这种情况下,它需要与 WHERE 子句成对出现。首先要匹配的节点需要定义它们的类型。在这个例子中,第一个节点需要是作者,第二个节点需要是书。然后,从每个节点获取特定属性必须在 WHERE 子句中。在作者下面的例子中,node 的名字必须是“Feser,Edward ”, node 的书名必须是“亚里士多德论方法和形而上学”。当选择属性来定义特定节点时,请确保它对于您想要的节点是唯一的。
CREATE (a:Author{name:'Feser, Edward'}), (b:Book{title:'Aristotle on Method and Metaphysics');MATCH (a:Author),(b:Book) WHERE a.name = 'Feser, Edward' and b.title='Aristotle on Method and Metaphysics' CREATE (a)-[:EDITS]->(b);
最简单形式的匹配语句可用于检索特定节点。
MATCH (n:Book) WHERE n.title='Aristotle on Method and Metaphysics' and n.year=2013 RETURN n;
Match 语句也可以用来返回具有相同标签的所有节点。
MATCH (a:Author) RETURN a;
为了构建上面的查询,还可以使用 match 语句找到一组具有特定关系的节点。基本上,图中的任何模式都可以用 match 语句找到。
练习查询
为了练习上面讨论的内容,创建一个新的数据库,并在 Neo4j 中运行链接到此处的语句来创建数据库。预期的输出显示在每个提示符下。
- 对于每个作者,列出他们的名字和文章标题(不包括章节或书籍)。

2.列出作者的姓名和各自发表的文章(文章、章节和书籍)数量。

3.列出 10 页或更少的文章的标题和页数。注意:pp 属性是一个列表。您可以使用括号来访问列表元素(例如 pp[0])

4.列出文章标题、发表文章的期刊标题,以及被引用两次或更多次的文章的作者姓名

答案
MATCH (n:Author)-[r:WRITES]->(x:Article) RETURN n.name,x.title;
MATCH (n:Author)-[:WRITES]->(r) RETURN n.name, count(*) UNION MATCH (n:Author)-[:EDITS]->(r) RETURN n.name, count(*);
MATCH (n:Article)-[a:IN]->(i:Issue) WHERE (a.pp[1] - a.pp[0]+1 ) <= 10 RETURN n.title, a.pp[1] - a.pp[0] +1;
MATCH (a)-[:CITES]->(b) With b, count(b.title) as publication_count WHERE publication_count>=2 With b.title as bt , publication_count MATCH (au:Author)-[:WRITES]-> (a:Article)-[:IN]->(:Issue)-[:OF]->(j:Journal) WHERE bt = a.title Return j.title, a.title, au.name,publication_count;
图形数据库正被广泛用于社交网络的欺诈检测。Neo4j 在图形数据库中越来越受欢迎。由于其高性能、最大和最活跃的图形社区以及生产应用程序,它从其他图形数据库中脱颖而出。
有关 Neo4j 的任何其他问题或附加练习查询,请联系。此外,查看 Neo4j 网站获取更多资源。
Microsoft Power BI —从数据建模到令人惊叹的报告
这个可视化工具能为你做什么?

图片由卢卡斯·布拉塞克在 Unsplash 上拍摄,作者进行了增强。
什么是 Power BI?能代替好 ol' 微软 Excel 吗?
还是他们一起工作更好?
创造惊人的观想有多容易?
Power BI 对 ETL 和数据建模也有好处吗?
我们将在这篇演示文章中回答这些问题。提供数据!
数据科学新手?
查看我的文章,关于分类 、流行的 绩效指标——以及人工智能如何被用于https://medium.com/swlh/math-modelling-and-machine-learning-for-covid-19-646efcbe024e,机器学习与传统的 数学模型 ,以及如何 企业****
加入 Medium 这里并获得无限制访问互联网上最好的数据科学文章。
Excel 与 Power BI
Excel 适合小型特别分析。它非常适合直接输入数据、组织数据和进行一些简单的可视化。
Power BI 是一款符合行业标准的商业智能和可视化工具,为企业提供易于创建的可视化仪表盘和报告。这些可行的见解让老板们能够快速做出明智的决策。
许多用户协同利用 Excel 和 Power BI。
将销售数据导出到 Excel 文件中,执行一些基本检查和分析,然后将。 xslx 进入 Power BI 创建一些漂亮的交互报表。

从 Excel 文件到炫酷的商务智能仪表盘!作者图片
微软的“动力平台”
Power BI 属于微软的 Power Platform 应用套件,其中包括 PowerApps (无需代码构建应用)和 Power Automate (业务流程自动化)。
总之,您可以构建一些强大的工作流。可能性是无限的。

微软“Power Platform”生态系统中的热门应用。作者图片
例如,您的公司是否拥有一些同事经常需要访问的数据?你是通过。无尽的电子邮件和手动流程?
构建一个 PowerApp 应用程序,作为同事的前端。不需要编码技能。
然后创建一个 Power Automate 流程,每当有人需要文件时,它会自动向你发送一个微软团队的 Approvals 请求。
所有的电子文书工作都被推送到一个 SharePoint 列表中,该列表可以输入到 Power BI 中进行按需分析和跟踪。
恭喜您,您已经将数小时的琐碎任务简化为只需几分钟的自动化工作流程,并在交互式 Power BI 仪表盘的支持下随时进行报告。
微软的 Power 平台和更广泛的微软 365 应用生态系统能够简化您的工作生活,不需要任何编码技能。
聪明地工作,而不是努力。
利用技术每天为您节省时间。让它成为一种习惯。
在一年中积累这些储蓄,这就是你如何成为一个在下午 5 点准时下班的生产率怪兽。
数据分析师的 Power BI 之旅
Power BI 中的典型工作流是什么样的?
业务分析师和数据科学家应该对这一系列步骤很熟悉。

作者图片
你将要
- 将数据输入 Power BI(摄取)
- 清理并准备用于分析(转换和建模)
- 发现见解(可视化)并与同事分享。
请注意,Power BI 有 3 个版本:
- Power BI Desktop (电脑上的本地应用)
- **电力 BI 服务(基于浏览器)
- 电力匕移动。
转换和建模步骤仅在桌面版本上可用。我将使用 Power BI Desktop ,这样我们可以端到端地演示整个工作流程。太好了!**
我们开始吧!
想跟着去吗?
我将使用流行的 AdventureWorks 数据集,你可以在这里下载:AdventureWorks sales . xlsx。
假设你的老板想知道以下问题的答案:
- 一年中的哪一天销售额最高?
- AdventureWorks 在哪些国家取得了最大的成功?
- 公司应该继续投资于哪些产品类别?
不容易回答,只是在 Excel 中争论——这是 Power BI 的魔力真正闪耀的地方。我们开始吧。

作者图片
1.摄取
打开 Power BI 并导入您的 Excel 电子表格。
浏览并选择您需要的列/字段,然后单击 Load …
等等!也许我们想对数据做进一步的修改?让我们点击转换数据。

数据导航。作者图片
快速提示:Power BI 可以连接很多数据源。**
SQL Server?没问题。
传统的数据仓库呢? Teradata 还是 Oracle ?你很棒。
使用大数据和/或云?你可以直接从微软的 Dataverse 、 Azure Cloud 或 Synapse Analytics 中抽取。
聪明的数据工程师甚至可能已经将 Power BI 直接连接到您组织的 Hadoop 大数据湖。精彩!
2.转型(仅限 Power BI 台式机)
这是您使用 Power BI 的 Power 查询编辑器来处理数据的地方。(你也可以在 Excel 中做很多这样的事情。)
简单转换的一个例子包括修改列/字段的数据类型。这里,邮政编码作为整数或文本会更好吗?通过点击几个按钮来决定。轻松点。****
根据您的业务需求进行更改。

超级查询编辑器。作者图片
一旦你满意了,就该对数据建模了。
3.建模(仅限 Power BI 台式机)
你的数据之间有什么关系?
在不同的表之间建立链接,以便以后可以连接它们。
您也可以在这里创建更多的字段。这包括精心设计定制的措施来提供有针对性的见解。很快会有更多。**
这是我们建模前的数据。

建模前的数据。作者图片
数据模型采用了一个星型模式,其中一个集中的事实表(存储关于销售的度量)与每个维度表(存储关于 AdventureWorks 客户、产品、经销商等的信息)直接相关。
请立即注意,日期维度表是独立的,与我的其他数据无关!哎呦。

典型的星型模式。图片鸣谢:微软 DP-500 公训
使用 Power BI 的 GUI 界面,我能够对我们的数据模型进行一系列修改:
- 将日期表与销售表相关联。唷!
- 为每个维度表创建一组层次列。例如,产品可以分为类别、子类别和型号。客户可以按照他们的国家、州、城市和邮政编码来组织。之后我可以使用这些新的专栏来更深入地挖掘我的观想!
- 隐藏所有用于表连接但对分析无用的'键列。**
- 重命名几张桌子,因为…我觉得很烦躁。
想做一些比点击按钮更个性化的事情吗?
您可以在 DAX (数据分析表达式)中编写自定义度量,这对于 Excel 公式大师来说看起来很熟悉。
例如,我用下面的 DAX 代码创建了一个新的度量标准截止日期的销售额。
**Sales Amount by Due Date = CALCULATE(SUM(Sales[Sales Amount]), USERELATIONSHIP(Sales[DueDateKey],'Date'[DateKey])**
我们最终的数据模型如下所示:

建模后的数据。作者图片
现在,我已经准备好了大家都知道的 Power BI 部分——漂亮的交互式仪表盘!
4.(英)可视化(= visualization)
这是 Power BI(以及 Tableau 等竞争对手)真正闪光的地方。
在右侧的可视化和字段面板之间拖放东西,以在几秒钟内制作交互式图形。

漂亮的报告和仪表板。作者图片
我上面的仪表板包含 3 个视觉效果:
- 面积图
- 地图
- 矩阵。

拖放右边的窗格。作者图片
例如,以面积图为例,它提供了 AdventureWorks 一段时间内的销售信息。
我首先将度量值 Sales Amount 拖到我的视图的 Y 轴上。
然后,我将月份字段拖到 X 轴上。(请记住,我们之前在建模过程中创建了这个基于层次结构的字段!如果我们没有做到这一点,我们就只能细化到财政年度。哎呦!)
友情提示:你可以随时返回并修改你的数据模型来创建你可视化所需要的东西。数据分析不是线性的!

创作我的第一个视觉作品。作者图片
我还在同一个图表上添加了自定义度量(来自 DAX) 截止到期日的销售额,并将其编码为红色(见下文)。
别忘了所有的图都是交互的!
这是现代商业智能可视化工具的强大功能。
例如,我可以从我们的仓库中挖掘自行车销售的数据。所有其他视觉效果及其相应的数据将立即更新,以反映我的选择。非常有用!

按特定细分市场深入分析销售数据。作者图片
或者,我可以跳到某个特定日期,了解整个企业的情况。同样,所有的视觉效果都会立即更新。

按特定日期钻研销售数据。作者图片
如果你正在向管理层提交一份报告,而管理层正在现场盘问你更多的细节,或者向听众讲述一个数据故事,而听众突然想挖掘一些更细粒度的见解,那么这是一个强大的东西。
这种触手可及的定制复杂数据洞察力的能力类似于二十年前的魔法。**
5.出版
得到了一些疯狂的见解,想与同事分享?
你可以用几种方法做到这一点。
例如,您可以将仪表板保存为 Power BI。 pbix 文件并通过电子邮件发送,或者发布到 Power BI 服务上,使其在整个微软 365 云网络中可用。
最后的话
Power BI 和 Tableau 等企业 BI 工具使任何人都能够创建令人惊叹的报告和仪表板,而无需具备编码经验。
如果您在一家利用微软生态系统的公司工作,您将有机会使用 Power BI,以及其更广泛的 Power Platform 应用套件,允许您创建和自动化工作流,并为您自己、您的同事,当然还有您的经理制作强大的可视化报告。

微软动力平台。来源:微软数据世界
商业软件已经走过了漫长的道路。
渴望详细了解这个演示吗?下载数据,Power BI 的试用版,并遵循此处的详细指南。
在 YouTube 和 Twitter 上关注我。
无限制媒体访问
提升你的知识和技能到一个新的水平。
加入 Medium 享受无限制访问互联网上的最佳分析&数据科学文章。你可以在这里加入来支持我和其他顶级作家。
我的数据科学文章
- 微分方程与机器学习— 此处
- 新冠肺炎的数学建模与机器学习— 此处
- 回归预测房价— 此处
- 分类预测员工流失— 此处
- 流行的机器学习性能指标— 此处
- Jupyter 笔记本与 Dataiku DSS — 此处
- Power BI —从数据建模到令人惊叹的报告— 此处
PyTorch 简介:第 1 部分
原文:https://towardsdatascience.com/intro-to-pytorch-part-1-663574fb9675
PyTorch 图书馆简介

亨特·哈里特在 Unsplash 上的照片
介绍介绍
PyTorch 基于 Torch 库,是最受机器学习从业者欢迎的深度学习框架之一。PyTorch 受欢迎的一些原因是它的易用性、动态计算图,以及它比 Tensorflow 等其他框架更“Pythonic 化”的事实。
在本教程中,我们将检查 PyTorch 的基本组件,然后使用 CIFAR10 数据集完成一个图像分类任务。因为 PyTorch 加载了大量的特性,并且有大量的方法来应用它们,所以这显然不是全面的。这篇文章的目的是介绍这个包以及您将使用的一些组件,并提供一些资源以便您可以继续您的旅程。
张量
PyTorch 的核心组件是张量数据结构。如果您熟悉 NumPy(如果您不熟悉,请查看我在关于数据科学中的文章), PyTorch tensors 类似于 NumPy ndarrays,主要区别在于它们支持 CUDA,并且构建为在硬件加速器上运行,如 GPU。张量拥有的另一个重要特征是,它们针对自动微分进行了优化,这是被称为反向传播的神经网络训练算法的基础。这两个优化对于深度学习至关重要:
- 深度学习通常包含的大量数据、特征和训练迭代需要 GPU 的大规模并行架构在合理的时间内进行训练
- 通过反向传播的训练需要有效和精确的区分
PyTorch 还支持分布式计算,将训练过程扩展到单台机器之外!
说了这么多,我们再来看看张量 API!
与张量一起动手
注意:如果您想继续这里的内容,请先跳到设置部分,这样您就可以用 Colab 编写代码了
我们可以从 Python 列表中自然地创建张量:
这也自然地适用于 Numpy ndArrays :
就像在 NumPy(和 Tensorflow)中一样,我们可以用随机值、全 1 或全 0 初始化张量。只需提供shape(如果您想要指定数据类型,则提供dtype):
别忘了张量不一定是二维的!
一个新的张量可以从现有的张量中产生。因此,如果我们愿意,我们可以创建一个新的零张量,它具有与我们创建的A_tensor相同的属性(形状和数据类型):
或者您可能想要随机的浮点值:
想要张量的属性吗?
创建张量是好的,但是真正的乐趣开始于我们可以开始操作它们和应用数学运算的时候。已经内置了大量简洁的张量运算,所以我们肯定没有时间一一介绍。相反,我会给你一个链接,让你更详细地查看它们,并且只列出几个:
- 矩阵乘法
- 计算特征向量和特征值
- 整理
- 索引、切片、连接
- 海明窗(不确定这是什么,但是听起来很酷!!)
数据集和数据加载器模块
资料组
像 Tensorflow 一样,PyTorch 的包中包含了许多数据集(包括文本、图像和音频数据集)。本教程的深度学习部分将使用这些内置图像数据集之一:CIFAR10。这些数据集非常常见,并在 ML 社区中被广泛记录,因此它们非常适合于原型和基准模型,因为您可以将您的模型的性能与其他人用他们的模型实现的性能进行比较。
这样,如果数据集有标注或分类,您可以快速查看这些标注或分类的列表:
显然,作为机器学习实践者,内置数据集并不是你需要的全部。尽管这个过程比仅仅导入一个数据集更复杂,但是用 PyTorch 创建自己的数据集是相当容易和灵活的。这已经超出了本文的范围,但是我将在不久的将来发布一个创建数据集的深入指南。
数据加载器
遍历数据集将逐个遍历每个样本,因此 PyTorch 为我们提供了 DataLoader 模块,可以轻松地在我们的数据集中创建迷你批处理。DataLoader允许我们指定batch_size以及混洗数据:
因此,在你的深度学习工作流程中,你会希望通过迷你批处理中的DataLoader将你的数据输入到你的模型中进行训练。
教程的深度学习部分将演示如何使用DataLoader并将其输入神经网络。
在我们进入深度学习之前,最后一个重要的功能是设置设备。当您想要在 GPU 上训练时,您可以检查是否有 GPU 可供 PyTorch 使用:
PyTorch 默认为 CPU,因此即使手头有 GPU,您仍然需要指定要使用 GPU 进行训练。如果你确定你的 GPU 是可用的,你可以使用。to(“cuda”)在您的张量和模型上。否则,您可以考虑将设备变量设置为任何可用的设备:
如果你使用 Google Colab,你将可以免费使用 GPU(除非你想订阅)。说到 Colab,让我们继续进行分类任务的设置!
设置
在本教程中,我们将使用 Google Colab。Colab 一直是我所有机器学习项目的首选,因为在我看来,没有比它更简单的设置了。显然,一些项目将需要不同的设置,但对于较小的项目和教程,你真的不能击败 Colab 的免费 GPU 访问和环境,其中已经包括像 PyTorch,NumPy,Scikit-Learn 这样的软件包。
首先,导航到 Google Colab 页面,用你的 Google 账户登录。File > New notebook。将顶部的笔记本名称更改为pytorchIntro.ipynb,或者您喜欢的其他名称。默认情况下,Colab 不会给你一个 GPU 访问的实例,所以你必须指定你想要使用的 GPU:在顶部,转到Runtime > Change runtime type > Hardware accelerator > Select "GPU" > Save。现在你有一个 GPU 来训练你的模型!
如果您对将要使用的 GPU 感到好奇,请键入
!nvidia-smi并通过点击该行左侧的 Play 按钮或按 Shift+Enter 来执行该行。如果您想要的只是 GPU 设备,也可以运行!nvidia-smi -L:
GPU 0: Tesla T4 (UUID: GPU-7619bc40-f58c-a507-3911-58907fbd2721)
现在您已经有了 Colab,并且有一个 GPU 准备好训练您的模型,让我们来看看代码。
进口
将这些导入添加到第一行,并执行该行。这些是我们将使用的主要 PyTorch 模块,以及一些支持导入。当我们使用它们的时候,我们会更详细地讨论它们。
资料组
CIFAR-10 数据集
该数据集由 60,000 幅 32x32 彩色图像组成,所有图像都被标记为 10 个类别中的一个。训练集是 50,000 幅图像,而测试集是 10,000 幅图像。
这是一个很好的来自家庭资源的数据集的可视化:

来源:https://www.cs.toronto.edu/~kriz/cifar.html
这个项目的目标是建立一个模型,可以准确地将图像分类为 10 种分类之一。
加载数据集
所以我们从 torchvision 导入了 CIFAR10,现在我们需要下载实际的数据集,并准备将其加载到神经网络中。
首先,由于我们应该在将图像提供给模型之前对它们进行归一化,所以我们将定义一个transform函数,并在创建训练和测试数据变量时使用torchvision.transforms.Normalize到归一化我们所有的图像。Normalize方法将期望的平均值和标准偏差作为参数,由于这些是彩色图像,因此应该为每个(R、G、B)颜色通道提供一个值。
我们将这里的值设置为 0.5,因为我们希望图像数据的值接近 0,但是还有其他更精确的归一化方法。
现在我们可以在 transform 参数中使用我们的 transform 函数,这样 PyTorch 就会将它应用到整个数据集。
既然我们已经下载并规范化了数据集,我们可以使用 PyTorch DataLoader 准备好将它提供给神经网络,在这里我们可以定义batch_size。
DataLoader是可迭代的,所以让我们通过检查一次迭代的维度来看看train_dataloader:
这里 X 是图像,y 是标签。我们设置了batch_size = 4,所以通过我们的train_dataloader的每一次迭代都是 4 个 32×32 图像和它们的 4 个相应标签的小批量。
现在让我们看看数据集中的一些例子。

现在我们可以看到一些图像和它们相应的标签。通常,您会希望在继续进行模型构建之前进行更彻底的数据探索和分析,但是由于这只是 PyTorch 的一个介绍,我们将继续构建和训练模型。
定义基础模型
让我们建立一个神经网络。
首先,我们将定义我们的模型类,并将其命名为NeuralNetwork。我们的模型将是 PyTorch nn 的子类。模块,它是 PyTorch 中所有神经网络模块的基类。
因为我们的数据集中有彩色图像,所以每个图像的形状是(3, 32, 32),3 个 RGB 颜色通道中的每一个都是 32×32 的张量。由于我们的初始模型将由完全连接的层组成,我们将需要 nn。Flatten() 我们输入图像数据。我们的展平方法将输出一个具有 3072 (32 x 32 x 3)个节点的线性层。nn.Linear()分别以输入神经元的数量和输出神经元的数量作为自变量(nn.Linear(1024 in, 512 out))。从这里您可以添加Linear层和ReLU层到您的心满意足!我们模型的输出是 10 个 logits,对应于我们数据集中的 10 个类。
在我们定义了模型的结构之后,我们将定义向前传递的顺序。由于我们的模型是一个简单的序列模型,我们的forward方法将非常简单。forward方法将从输入Tensors计算输出Tensor。
如果你愿意,你可以简单地打印出定义好的model,这样你就可以得到一个结构的概要。
损失函数和优化器
由于这是一个分类问题,我们将使用交叉熵损失函数。提醒一下,当模型输出介于 0 和 1 之间的预测概率值时,交叉熵计算对数损失。因此,当预测的概率偏离真实值时,损失会迅速增加(如果预测更有把握,错误的预测会受到更多惩罚)。下图显示了预测值越来越接近真实值时损失函数的行为。

图:y 轴代表损失,x 轴是真值为 1 时的预测值。可以看到,随着预测值趋近于 1,损耗趋近于 0。预测值越接近 0,损失值越高。
有了 PyTorch,我们只需使用CrossEntropyLoss()。对于其他 ML 任务,如果更合适,可以使用不同的损失函数。对于我们的优化算法,我们将使用随机梯度下降,这是在 torch.optim 包中实现的,以及其他优化器,如 Adam 和 RMSprop。我们只需要传递我们模型的参数,以及学习率lr。如果您想在模型优化中使用动量或重量衰减,您可以将其传递给SGD()优化器以及momentum和weight_decay参数(都默认为 0)。
定义训练循环
这里我们定义了我们的train()函数,在训练过程中我们将传递train_dataloader、model、loss_fn和optimizer作为参数。size变量是整个训练数据集的长度(50k)。在下一行,model.train()是一个 PyTorch nn.Module方法,它将模型设置为训练模式,启用您在训练期间想要的某些行为(例如,退出、批量规范等。).相比之下(当我们定义测试函数时你会看到),如果你想测试你的模型性能,你可以使用model.eval()。接下来,我们将遍历每个小批量,指定我们希望使用带有to(device)的 GPU。我们将小批量输入到模型中,计算损耗,然后反向传播。
反向传播和训练进度输出
对于反向投影步骤,我们需要首先运行optimizer.zero_grad()。这将在开始反向投影之前将梯度设置为零,因为我们不想在后续过程中累积梯度(这种行为在某些情况下可能是需要的,例如 RNNs,您需要累积梯度)。loss.backward()使用损失来计算梯度,然后我们使用optimizer.step()来更新权重。最后,我们可以打印出训练过程的更新,输出每 2000 个训练样本后计算出的损失。
定义测试方法
在训练模型之前,让我们实现测试函数,这样我们可以在每个历元之后评估我们的模型,并在测试集上输出准确度。测试方法的最大区别是,我们使用model.eval()将模型设置为测试模式,而torch.no_grad()将禁用梯度计算,因为我们在测试期间不使用反向传播。最后,我们计算了测试集的平均损失和总体精度。
训练我们的模型
现在我们已经加载并预处理了数据集,构建了神经网络,定义了损失函数/优化器/训练循环…我们准备好训练了!指定您想要训练模型的epochs的数量。每个历元将经历一个train循环,每 2000 个样本输出一次进度,然后在测试集上test模型,在每个历元后输出测试集上的精度和损耗。

这是每个训练时段的输出结果
保存和加载模型
训练完成后,如果您想保存您的模型用于推理,请使用torch.save()。将model.state_dict()作为第一个参数传递;这只是一个 Python 字典对象,它将层映射到它们各自的学习参数(权重和偏差)。对于第二个参数,命名您保存的模型(使用.pth或.pt扩展保存 PyTorch 模型是常见的约定)。如果您希望将此参数保存在特定位置,也可以为其指定完整路径。
当你想加载你的模型进行推理时,用torch.load()抓取你保存的模型,用load_state_dict映射学习到的参数。
评估模型
您可以遍历test_dataloader来检查带有标签的图像样本。

然后将其与我们模型的预测标签进行比较,以预览其性能:
所以我们可以看到,我们的模型似乎在学习分类!让我们看看我们模型的性能数字。
53% 的准确率不是最先进的,但是比随机猜测或者只预测一类要好得多,所以我们的模型肯定学到了一些!😃
接下来,我们可以快速检查它在分类每个类时的表现:
所以现在我们对模型的性能有了更好的了解:猫和鸟的图像对网络来说更难分类。
未完待续…
显然,像我们在本教程中构建的全连接网络通常不用于图像分类。在本教程的第 2 部分,我们将更多地关注 PyTorch 中的性能优化:
- 使用细胞神经网络进行图像分类
- 超参数调谐
- 数据扩充
- 迁移学习
我希望你喜欢,并学到了一点。如果你想了解更多,PyTorch 有一些优秀的文档,所以我鼓励你去看看!感谢阅读!

PyTorch 挺牛逼的:)
放射学标签提取语句分析简介(SARLE)
SARLE 是一种可定制的技术,用于从自由文本放射学报告中自动提取结构化异常和位置标签

特色图片由这张来自维基百科(CC BY-SA 3.0)的纸质图片和这张来自维基百科(Creative Commons 1.0)的胸透图片组成。
指定异常存在与否的分类标签对于在放射图像上训练计算机视觉模型是必要的。然而,手动获得这些分类标签是耗时的,并且限制了最终数据集的大小以及可以考虑的异常的数量。在这篇文章中,我们将概述一种易于定制的技术,SARLE,用于从医院数据库中每个放射学图像附带的自由文本报告中自动提取结构化异常和位置标签。
什么是放射学报告?
每次给病人成像时,放射科医生都会解读图像,并写一份总结正常和异常发现的报告。胸部 x 光报告的摘录可能是这样的:“右肺有一个结节。左肺没问题。有心脏扩大而无心包积液。”
什么是放射学标签提取?
放射学标签提取是从自由文本放射学报告中获取二元异常标签的过程。这比仅仅识别是否提到异常更复杂,因为放射科医生通常会特别注意到不存在的异常或相对于先前图像已经解决的异常。例如,“毛玻璃样阴影已消失”,“胸腔管已取出”,或“左肺上叶结节不再被识别”。
为什么放射标签提取困难
放射学标签提取是困难的,原因有几个:
(1)如前所述,一些异常被包括在报告中是因为它们存在,而其他异常被记录是因为它们不存在,这意味着需要否定/正常检测;
(2)存在数百种可能的异常,并且它们中的许多具有同义词或几种不同的描述方式(例如,增大的心脏= =心脏增大,胸膜积液= =胸膜腔中的液体),
(3)有许多描述性修饰术语(例如,指示纹理、一般尺寸、测量尺寸、相对于先前图像的尺寸、严重性等),并且这些描述符中的一些可以是正常或异常之间的差异。例如,“淋巴结病”或肿大的淋巴结由大于 1 厘米的大小来确定。淋巴结病可被描述为“淋巴结病”、“腺病”、“肿大的淋巴结”,或简单地用一个测量值,“1.7 厘米纵隔淋巴结”或“右腋窝 22 毫米淋巴结”。有时会提到边界增大的淋巴结(例如,“9 mm 淋巴结”),其他时候会提到曾经很大但现在正常的淋巴结(例如,“以前 1.1 cm 的淋巴结现在测量为 0.3 cm”)。
(4)如果您对异常的解剖位置感兴趣,某些解剖位置也有同义词(例如,“左上叶= =左上叶”),并且一些位置仅由异常的性质暗示(例如,肺炎根据定义是肺部感染,心脏肥大根据定义是心脏增大)。
为什么放射学标签提取比处理其他种类的自然语言更容易
另一方面,放射学标签提取的某些方面使得它比自然语言上的一般摘要任务更容易。放射学笔记专注于相对狭窄的主题,这限制了所使用的短语的种类(相对于,比如说,一本小说或一本诗集),放射学笔记通常具有良好的语法、拼写和句子结构。
为什么放射学标签提取在计算机视觉中有用
计算机视觉模型在数据越多的情况下表现越好。他们还需要结构化标签进行训练,无论是存在/不存在标签的二进制向量来训练分类模型,还是像素级追踪(分割图)来训练分割模型。让我们考虑一下最容易获得的标签类型:分类标签。如果我们想要建立一个分类器数据集,包括 100,000 张胸部 x 光片中的 50 种不同异常,我们将需要一名放射科医师手动记录 5,000,000 个标签。按每个标签一秒钟计算,就是 1388 个小时,相当于 173 个每天八小时的非常非常乏味的工作,为一种成像设备生成一个数据集。因此,人们对自动放射学标签提取非常感兴趣,它可以从现有的自由文本报告中自动生成我们需要的分类标签,而手动工作要少得多。
放射学标签提取方法
有不同的方法对标签提取方法进行分类:
方法:两种主要的方法是基于规则的和基于机器学习的。我记得对在这个大规模语言神经网络时代任何人都会使用规则的想法嗤之以鼻,但事实证明,规则可以很好地用于放射学标签提取,因为放射学笔记组织良好,并专注于有限的主题。
输入数据:方法的输入可以是一个完整的注释、一个完整的句子或一个短语。
考虑的标签:该方法可以只考虑一个、几个或多个异常标签。该方法甚至可以根本不考虑异常标记,例如,它可以只关注解剖位置。或者,该方法可以考虑异常和位置(例如 SARLE)。
本文的附录表 B2 中总结了放射标签提取方法的几个例子。

图片作者。SARLE 徽标包括名词项目中的知识共享图标:计算机、报告和表格。
SARLE:用于放射标签提取的句子分析
SARLE 是一个公开可用的高性能、易于定制的 Python 框架,用于放射学标签提取。它快速、易用、易于适应新类型的放射报告或新标签集,并且具有最小的依赖性。核心逻辑是不到 300 行的 Python 代码。它也是唯一一个提取异常和位置的放射学标签提取框架(据我所知):为每个异常提供一个相应的位置,这意味着标签输出实际上是一个位置 x 异常矩阵,而不是一个异常向量。
萨勒有两个步骤:
在第一步中,句子分类器区分正常句子(描述正常发现或没有异常)和异常句子(描述异常发现的存在)。所有正常的句子都被丢弃。SARLE 有两种变体:在 SARLE-Hybrid 中,机器学习分类器执行句子分类,在 SARLE-Rules 中,基于规则的方法执行句子分类。
第二步是术语搜索。所有异常的句子都被输入到一个术语搜索中,该搜索使用医学同义词来识别异常和解剖位置的提及。因为只剩下不正常的句子,任何提到不正常的地方都表明它存在。

作者图片

作者图片
从放射学报告中提取的 83 种异常情况如下所示:
肺 (22):气房疾病、空气潴留、误吸、肺不张、支气管壁增厚、支气管扩张、细支气管扩张、细支气管炎、支气管炎、实变、肺气肿、血胸、间质性肺病、肺切除术、粘液堵塞、胸腔积液、浸润、胸膜增厚、肺炎、肺炎、气胸、肺水肿、散在结节、间隔增厚、结核
肺型 (5):带状或线状、毛玻璃、蜂窝状、网状、树芽状
一般 (47):关节炎、动脉粥样硬化、动脉瘤、乳房植入物、乳房手术、钙化、癌症、导管或端口、空洞、夹伤、充血、囊肿、碎片、畸形、密度、扩张或扩张、膨胀、纤维化、骨折、肉芽肿、五金件、疝气、感染、炎症、病变、透亮、淋巴结病、肿块、结节、结节> 1 cm、混浊、斑块、手术后疤痕、散在钙化、分泌物、软组织、缝钉、支架、缝合、移植、胸管、气管导管、胃肠管(包括
心脏 (9): cabg(冠状动脉旁路移植术)、心脏肥大、冠状动脉疾病、心力衰竭、心脏瓣膜置换术、起搏器或除颤器、心包积液、心包增厚、胸骨切开术
SARLE 提取的 51 个位置是:
- 肺:左上叶、舌、左下叶、右上叶、右中叶、右下叶、右肺、左肺、肺间质、小叶中心、胸膜下、气道。
- 心脏:心脏,二尖瓣,主动脉瓣,三尖瓣,肺动脉瓣。
- 大血管:主动脉、上腔静脉、下腔静脉、肺动脉、肺静脉。
- 一般:右、左、前、后、上、下、内、外侧。
- 腹部:腹部、食道、胃、肠、肝、胆、肾、肾上腺、脾、胰。
- 其他:甲状腺、乳腺、腋窝、胸壁、肋骨、脊柱、骨骼、纵隔、膈、肺门。
性能
使用手动获得的基础事实,对 9 个标签的 427 个胸部 CT 报告分析了 SARLE 的性能。如下表所示,SARLE 实现了高性能:

按作者分类的表格
总体而言,SARLE-Rules(使用规则进行句子分类)优于 SARLE-Hybrid(使用机器学习进行句子分类)。这很可能是因为 SARLE-Rules 在技术上执行短语级分类,即它能够识别正常或异常句子的子部分,而不是在整个句子级别实现的 SARLE-Hybrid。这对于既提到正常发现又提到异常发现的句子特别有效,例如“有心脏增大但没有心包积液。”
顺便提一下,SARLE 的术语搜索步骤包括一些复杂的规则,用于处理依赖于测量的异常,如前面提到的淋巴结病示例。
萨勒码
萨勒代码在此公开:https://github.com/rachellea/sarle-labeler
代码是结构化的,因此很容易使 SARLE 适应您自己的数据集、您自己的异常和您自己的解剖位置。
脚本 demo.py 包括一个关于真实数据和虚假数据的 SARLE 演示:
- 真实数据:SARLE 在胸透报告的 OpenI 数据集上演示。
- 假数据:SARLE 在一些手工制作的假数据的小数据框上演示,以简单的方式演示数据格式。
SARLE 接收熊猫数据帧作为输入。存储库的 README 和 demo.py 中提供了有关其格式的详细信息。
要自定义 SARLE 检测到的异常,您可以直接编辑词汇文件:
- src/vocab/vocabular _ CT . py
- src/vocab/vocabulary_cxr.py
如果您想自定义 SARLE 的位置,您可以在:
- src/vocab/vocabulary _ locations . py
论文
要了解更多关于 SARLE 的细节,或者引用 SARLE 的话,你可以看看这篇论文:“基于机器学习的大规模胸部计算机断层扫描量多异常预测”
总结
SARLE 是一个用于放射学标签提取的高性能框架,以 Python 代码的形式公开提供,具有最小的依赖性。很容易适应新的数据集,并根据您自己的异常和感兴趣位置列表对其进行定制。如果您对在自己的数据集上部署 SARLE 有任何问题,请随时通过这个联系页面联系我!

原载于 2022 年 7 月 21 日 http://glassboxmedicine.com**。
介绍 Fast Dash——一个快速开发 ML 原型的框架
Fast Dash 使机器学习 web 应用程序的原型制作和部署速度快如闪电!

作者图片
TL;速度三角形定位法(dead reckoning)
Fast Dash 使机器学习 web 应用的原型化变得轻而易举。使用 Fast Dash,数据科学家可以用不到十行代码为他们的模型构建一个用户界面。
下面是构建一个简单的文本到文本模型原型的简单方法:

由作者使用 https://carbon.now.sh/生成
这是结果:

一个简单的文本到文本的网络应用程序。作者图片
要安装快速仪表板,
*pip install fast-dash*
主要特点:
- 多个同时输入和输出组件。
- 支持所有 Dash 组件—文本、音频、视频、图像。
- 高度可定制和可扩展。
- 快速构建和迭代。
- 详细的文档。
- 开源且免费使用。
资源
源代码:https://github.com/dkedar7/fast_dash/
请继续阅读,了解 Dash 的工作速度、主要用途、开发计划以及您可以做出的贡献。
如何使用
让我们看看如何使用 Fast Dash 开发一个简单的文本到文本的原型。我们的简单函数将一些文本作为参数,并返回相同的文本。Fast Dash 可以通过三个简单的步骤将此功能部署为原型:
- 定义你的推理函数。
推理函数或回调函数是一个将一个或多个输入值作为参数并输出一个或多个值的函数。输入和输出可以是任何有效的 Python 数据类型。在我们简单的文本到文本示例函数中,我们有一个输入和一个输出。我们的推理函数看起来像这样:
def text_to_text(input_text):
return input_text
2。选择您的输入输出组件并初始化应用程序。
快速仪表板在fast_dash.Components子模块中提供了一个Text组件。让我们把它作为输入和输出部分。使用这些细节,让我们也初始化我们的应用程序。
from fast_dash import FastDash
from fast_dash.Components import Textapp = FastDash(callback_fn=text_to_text,
inputs=Text
outputs=Text)
3。部署!
这是最简单的一步。运行应用程序即可。
app.run()
构建一个简单的原型只需要 6 行代码。下面是完整的代码:
from fast_dash import FastDash
from fast_dash.Components import Text# 1\. Inference function
def text_to_text(input_text):
return input_text# 2\. Define components and initialize app
app = FastDash(callback_fn=text_to_text, inputs=Text, outputs=Text)# 3\. Deploy!
app.run()
结果是一个好看的应用程序!

作者图片
设计你的应用
Fast Dash 还允许设置额外的设计选项——标题、图标、副标题和社交品牌。因此,让我们更改第 2 步代码,添加这些细节:
...
app = FastDash(callback_fn=text_to_text,
inputs=Text,
outputs=Text,
**title**='Simple text to text',
**title_image_path**='[https://tinyurl.com/mr44nn5y'](https://tinyurl.com/mr44nn5y'),
**subheader**='Build ML prototypes lightning fast!',
**github_url**='[https://github.com/dkedar7/fast_dash/'](https://github.com/dkedar7/fast_dash/'),
**linkedin_url**='[https://linkedin.com/in/dkedar7/'](https://linkedin.com/in/dkedar7/'),
**twitter_url**='[https://twitter.com/dkedar7/'](https://twitter.com/dkedar7/'))app.run()

作者图片
而且我们还可以从各种主题中选择!这里有一个我最喜欢的主题的例子,它让我们的应用看起来很粗略:
...
app = FastDash(callback_fn=text_to_text,
inputs=Text,
outputs=Text,
title='Simple text to text',
title_image_path='[https://tinyurl.com/mr44nn5y'](https://tinyurl.com/mr44nn5y'),
subheader='Build ML prototypes lightning fast!',
github_url='[https://github.com/dkedar7/fast_dash/'](https://github.com/dkedar7/fast_dash/'),
linkedin_url='[https://linkedin.com/in/dkedar7/'](https://linkedin.com/in/dkedar7/'),
twitter_url='[https://twitter.com/dkedar7/'](https://twitter.com/dkedar7/'),
**theme='SKETCHY'**)app.run()

作者图片
快速仪表板组件是FastComponent的对象。到目前为止,我们只使用了Text组件。但是快速破折号允许将任何破折号组件转换成 T2!
这还不是全部。因为我已经安装了 Google Cloud CLI,所以我可以简单地从这些示例的根目录运行gcloud run deploy,并将我的 Fast Dash 应用程序部署到 Google Cloud!
更多的例子
- 简单图像到图像原型
下面是一个简单的图像到图像代码的输出(输出按原样显示上传的图像):

作者图片
只需要这段代码:
from fast_dash import FastDash
from fast_dash.Components import Image, UploadImage# 1\. Inference function
def image_to_image(image):
return image# 2\. Define components and initialize app
app = FastDash(callback_fn=image_to_image,
inputs=UploadImage,
outputs=Image,
title='Simple image to image',
title_image_path='[https://tinyurl.com/mr44nn5y'](https://tinyurl.com/mr44nn5y'),
subheader='Build ML prototypes lightning fast!',
github_url='[https://github.com/dkedar7/fast_dash/'](https://github.com/dkedar7/fast_dash/'),
linkedin_url='[https://linkedin.com/in/dkedar7/'](https://linkedin.com/in/dkedar7/'),
twitter_url='[https://twitter.com/dkedar7/'](https://twitter.com/dkedar7/'),
theme='FLATLY')# 3\. Deploy!
app.run()
2。使用附加组件
我们可以通过借用本教程的代码来构建一个神经风格转移原型。这段代码中的新组件是一个下拉菜单。Fast Dash 可以用一行代码将其转换成一个FastComponent。这款应用的源代码可在这里获得。

作者图片
3。分子 3d 浏览器
此外,Dash 还附带了一个奇妙的 Dash 生物库,可以轻松渲染 3D 生物分子。一旦我们将这些 DashBio 组件转换为 FastComponents,我们就有了一个快速简单的原型!点击查看源代码。这个例子的灵感来自于 Dash 应用程序库的一个教程。

作者图片
将来的
Fast Dash 对于日常使用情况非常出色,但截至 2022 年 4 月,它仍处于开发的早期阶段。请在 GitHub 上分享任何反馈、错误或功能请求。我也很想听听你打算如何使用它以及你的经历。您的反馈将使 Fast Dash 更有用。
Fast Dash 是为原型打造的,不要与概念验证或 MVP 混淆。一些计划的开发工作将允许用户从许多不同的布局选项、表格数据组件和地理空间应用程序中进行选择。加入这里的列表,了解主要更新。

加入社区:https://fastdash.app/.作者图片
动机
让利益相关者能够轻松、广泛地访问他们的机器学习模型,是数据科学家今天面临的最大挑战。可访问性是获得反馈的关键,这允许快速迭代和开发。
利益相关者和领域专家也倾向于不信任模型,如果他们没有参与模型开发过程的话。
此外,数据科学家构建和部署 web 应用程序的能力决定了他们的工作会被广泛采用。毫无疑问,部署洞察力已经成为一项基本技能。
Fast Dash 是我帮助数据科学家在不编写大量代码的情况下实现其见解的尝试。因为它抽象了编写 UI 代码的需要,所以我们只需要指定需要在输入和输出组件中显示的数据类型。
今天加入榜单和pip install fast-dash!

作者图片
介绍神游—减少 PySpark 显影剂的摩擦
原文:https://towardsdatascience.com/introducing-fugue-reducing-pyspark-developer-friction-a702230455de
提高开发人员的工作效率,降低大数据项目的成本
这篇文章的最初版本发表在 James Le 的博客 这里 。它已经更新,包括新的赋格功能。

塞萨尔·卡里瓦里诺·阿拉贡在 Unsplash 上的照片
赋格的动机
数据从业者通常从使用 Pandas 或 SQL 开始。处理的数据量迟早会超出熊猫的处理能力,分布式计算就变得必不可少。Spark 就是这样一个工具,它是一个流行的分布式计算框架,能够在一个机器集群上处理内存中的大量数据。虽然 Spark 引擎在扩展数据管道方面非常强大,但新用户,甚至是有经验的用户,在使用 Spark 时都会面临许多陷阱。
预计最初的困难是必须学习一个全新的框架。Spark 和 Pandas 的语法和用法很不一样。将项目从 Pandas 迁移到 Spark 的用户经常会发现自己要重新编写大部分代码,即使是完全相同的应用程序逻辑。更糟糕的是,一些在 Pandas 中微不足道的操作在 Spark 中变得更加困难,并且需要一段时间才能实现。
这种差异的一个简单例子是获取每组数据的中位数。在熊猫身上,得到各组的中位数是不需要三思的。不过在 Spark 上,操作并不简单。我们在下面的代码片段中比较了两个框架的语法:
熊猫对星火的分组中位数
这种语法差异是因为在分布式设置中计算中值的开销很大。属于一个组的所有数据都需要移动到同一台机器上。因此,在获得中位数之前,需要对数据进行洗牌和排序。为了降低计算成本,可以用指定的容差获得近似的中值。在上面的代码片段中,20 是精度,意味着相对误差可能是 1/20,或 5%。指定容差允许用户平衡精度和速度。
除了语法上的差异,分布式环境中还有一些重要的概念(比如分区、混排、持久化和惰性评估)Pandas 用户最初并没有意识到。这些概念需要大量的时间来学习和掌握,这使得很难充分利用火花发动机。
Fugue ,一个开源的抽象层,提供了从单机到分布式计算设置的无缝过渡。使用 Fugue,用户可以用原生 Python、Pandas 或 SQL 编写他们的逻辑,然后将其带到 Spark(或 Dask)引擎执行。这意味着用户甚至不需要学习 Spark 语法就可以使用 Spark。

图片作者:神游 logo
本文将讨论 Spark 用户面临的棘手问题以及 Fugue 如何解决这些问题。神游是几年来对如何改善 Spark 开发者体验的质疑的直接结果。除了为 Spark 中的编码提供更简单的接口,使用抽象层还带来了更多实实在在的好处。在这里,我们将展示如何赋格:
- 处理不同计算框架(Pandas、Spark 和 Dask)之间的不一致行为
- 允许跨熊猫大小和 Spark 大小的数据重用代码
- 极大地加快了测试速度,降低了项目总成本
- 让新用户使用 Spark 更快地提高工作效率
- 提供能够处理端到端工作流的 SQL 界面
熊猫和火花之间的矛盾
大小数据能否有一个统一的接口?
过渡到 Spark 的 Pandas 用户经常会遇到不一致的行为。首先,Pandas 允许混合列类型。这意味着字符串和数字可以在同一列中混合使用。在 Spark 中,模式是严格执行的,不允许混合类型的列。这是因为 Pandas 在执行操作时可以看到所有的数据,而 Spark 在保存不同部分数据的几台机器上执行操作。这意味着如果没有严格执行模式,Spark 很容易让不同的分区表现不同。
熊猫和 Spark 对空值的处理也不同。下表总结了空值的默认处理方式

熊猫大战火花中的空处理
这是使用 Fugue 作为抽象层的第一个好处。让熊猫代码在 Spark 上运行是一回事,但让代码在计算引擎之间给出一致的结果是一个非常乏味的过程。在很多情况下,必须编写额外的代码来获得相同的结果。神游照顾到了一致性,在熊猫和 Spark 之间建立了一致的桥梁。Fugue 被设计成与 Spark 和 SQL 一致,因为这保证了代码在分布式设置中会像预期的那样工作。用户不应该花时间担心特定于框架的行为。
逻辑和执行的分离
为什么我需要在开始一个数据项目之前选择一个框架?
使用 Pandas 和 Spark 的一个难点是逻辑与接口紧密耦合。这是不切实际的,因为它要求数据从业者在项目开始时选择他们要用什么来编码。这里有两个场景,是同一个问题的两面。
- 一个用户在熊猫里面编码,然后数据变得太大。为了解决这个问题,必须升级底层硬件以支持执行(垂直扩展)。
- 一个用户在 Spark 中编码,期望数据很大,但是它从来没有增长到需要 Spark 的大小。由于 Spark 开销,代码和测试的运行速度比实际速度要慢。
在这两种情况下,用户最终都使用了错误的工具。如果逻辑和执行分离,这些场景是可以避免的。使用 Fugue 作为抽象层允许用户编写一个与 Pandas 和 Spark 都兼容的代码库。然后可以在运行时通过传递执行引擎来指定执行。为了演示这一点,让我们来看看使用 Fugue 最简单的方法,即transform()函数。
对于这个例子,我们有一个包含列id和value的数据帧。我们想通过将value映射到mapping中相应的食物来创建一个名为food的列。
我们问题的简单设置
熊猫对此有一个简单的方法。我们可以创建一个熊猫函数来调用它。
使用熊猫来执行地图操作
不用编辑熊猫函数,我们可以用 Fugue 的transform()函数把它带到 Spark。这个函数可以接受 Pandas 数据帧或 Spark 数据帧,如果使用 Spark 引擎,它将返回 Spark 数据帧。
用赋格变换移植一个函数到 Spark
注意我们需要调用.show(),因为 Spark 的求值是延迟的。输出如下所示。
+---+-----+------+
| id|value| food|
+---+-----+------+
| 0| A| Apple|
| 1| B|Banana|
| 2| C|Carrot|
+---+-----+------+
在这个场景中,我们不需要编辑最初的基于 Pandas 的函数。transform()函数负责将执行移植到 Spark,因为我们提供了一个spark_session作为引擎。如果没有指定engine,则使用默认的基于 Pandas 的执行引擎。Pandas 用户可能不习惯显式定义模式,但这是分布式计算的一个要求。
但实际上,熊猫并不总是最容易表达逻辑的方式。因此,通过灵活处理不同的输入和输出类型,Fugue 还支持使用原生 Python 函数。下面是我们的map_letter_to_food()函数的三种不同实现。它们都与 Fugue transform()函数兼容,可以在 Pandas、Spark 和 Dask 引擎上使用相同的语法。
我们函数的不同实现,都与赋格兼容
注意所有的逻辑都是在map_letter_to_food()函数中定义的。然后执行被推迟到我们指定引擎的transform()调用。用户只需要关心以他们喜欢的方式定义他们的逻辑。然后,Fugue 会把它带到指定的执行引擎。
Spark 提供了pandas_udf作为在 Spark 上执行 Pandas 功能的方式,而 Fugue 则提供了一个更简单的模式接口。模式管理最终会在 Spark 中产生大量样板代码。这里,模式以一个最小的字符串传递给transform(),保留原来的函数定义不变。此外,如果用户指定,Fugue 可以使用 pandas_udf,在这种情况下使用 Fugue 的开销不到一秒,正如在基准测试中看到的。
在更实际的层面上,对于数据科学团队来说,拥有包含用于清理和转换数据的特定业务逻辑的共享库是非常常见的。目前,该逻辑必须实现两次——一次用于熊猫规模的项目,另一次用于 Spark 规模的项目。通过使用 Fugue,相同的功能可以在 Pandas 和 Spark 引擎上使用,而无需任何代码更改。
这也是面向未来的代码。如果有一天,你决定要使用 Dask 引擎呢?如果你想使用光线引擎呢?使用 Fugue 作为抽象层可以让您无缝迁移,因为这只是在运行时指定执行引擎的问题。另一方面,使用 Spark API 编写代码会自动锁定该框架的代码库。如果用户想的话,Fugue 的极简界面有意地使其易于离线。
提高火花的可测试性
我们如何加快大数据项目的开发迭代和测试?
在 Spark 中测试代码是乏味的。目前有两种方法用于开发 Spark 应用程序。Databricks 上的用户可以使用databricks-connect Python 库,它取代了 PySpark 的本地安装。每当调用pyspark时,执行计划都在本地编译,然后在配置好的集群上执行。这意味着简单的测试和代码更改需要启动后端集群。这需要一段时间,也非常昂贵。
第二种方法是在本地开发,然后使用 spark-submit 工具打包代码,并通过 SSH 在集群上运行。这个过程需要更多的工作和时间。对于进行测试驱动开发的团队来说,整个测试套件可能需要很长时间来测试。即使所有的测试都在本地完成,Spark 与 Pandas 相比仍然运行缓慢,因为需要设置 JVM 环境。对数据帧操作上的值的断言需要一个collect()或toPandas()调用,与基于 Pandas 的评估相比,这将花费大量时间。
因为在运行时选择执行引擎,我们可以在测试期间对较小的数据使用基于 Pandas 的引擎,然后将 Spark 引擎用于生产。测试变得更快更便宜,因为代码与 Spark 解耦,这意味着 Spark 运行时不必为每个小代码测试而加速。在用 Pandas 进行本地测试之后,同样的代码可以被带到 Spark 执行引擎中进行扩展。
Fugue 保证的一致性确保了在默认引擎上运行和在 Spark 执行引擎上运行提供相同的结果。这种分离极大地加快了开发周期,并使大数据项目的成本大大降低,因为可以避免代价高昂的错误。测试时间通常从几分钟减少到几秒钟。
Fugue 的用户也受益于不得不编写更少的测试。在我们上面的transform()例子中,只有原始函数需要被测试。用户也可以测试transform(),不过已经在赋格级别上重重测试过了。相比之下,使用 PySpark 方法将需要 1 或 2 个助手函数,然后也必须进行测试。与transform()等价的 PySpark 代码片段可以在这里找到。
减少代价高昂的错误
怎样才能减少分布式计算初学者面临的摩擦?
许多 Spark 用户并不知道在 Spark 中重新计算数据是非常容易的。分布式计算框架延迟评估代码,这意味着计算图(或 DAG)被构造,然后在执行一个动作以具体化一个结果时被执行。动作是像打印或保存数据帧这样的操作。
在下面的计算图中,针对运行 C、D 和 e 的操作重新计算 B。这意味着它被计算三次。如果 B 的一次运行需要一个小时,我们就不必要地给工作流程增加了两个小时。

作者图片:DAG 示例
有经验的 Spark 用户会知道 B 应该被持久化以避免重新计算。然而,不太熟悉懒求值的人往往会遭受不必要的重算。在极端情况下,当操作不确定时,这种懒惰的评估和重新计算会导致意外的行为。最明显的例子是 B 中包含随机数的列。如果 B 没有被持久化,那么 C、D 和 E 的随机数列将被重新计算,得到不同的结果。
为了解决这个问题,神游在工作流程层面也有优化。之前我们展示了单一功能的 Fugue 的transform()界面。Fugue 还支持通过使用FugueWorkflow()构建完整的工作流,如下所示。这是整个工作流的引擎无关的 DAG 表示。FugueWorkflow()可以像前面显示的transform()功能一样接受一个引擎,将其移植到 Spark 或 Dask。
富格工作流示例
通过分析构建的计算图(DAG)的依赖关系,Fugue 可以灵活地持久存储将被多个操作重用的数据帧。为了更好地控制,Fugue 还为用户提供了一个接口来保存数据帧。
通过这个 DAG,Fugue 还可以执行允许代码快速失败验证(比如模式和分区)。如果模式与未来的操作不匹配,Fugue 会识别出来,并立即出错。许多 Spark 用户经常花费大量金钱和时间在集群上运行代码,几个小时后才发现它失败了。拥有 Fugue 的 DAG 编译过程有助于用户避免代价高昂的错误。
SQL 接口
如何将 SQL 提升为计算工作流的一级语法?
Spark 的创新之一是 SparkSQL 中的 SQL 接口。SparkSQL 接口非常适合喜欢 SQL 的人描述他们的计算逻辑。不幸的是,它不允许用户利用 Spark 提供的所有功能,因为它紧密地基于 ANSI SQL。它还是第二类接口,通常在主要基于 Python 的代码之间调用。
Fugue 有一个基于 SparkSQL 实现的 SQL 接口,但是增加了一些增强功能。首先,有一些额外的关键字,如 BROADCAST、PERSIST、PREPARTITION 和 PRESORT,允许用户明确地利用 Spark 的分布式计算操作。还支持通过 TRANSFORM 关键字将 Python 函数与 FugueSQL 一起使用(等等)。添加了加载和保存等更多关键字,以支持端到端工作流。下面相当于我们之前的FugueWorkflow。
FugueSQL 示例
现在,大量 SQL 用户可以在 Spark 引擎上使用 FugueSQL 加载数据、执行转换和保存结果。SQL 爱好者可以在类似 SQL 的界面中表达他们的端到端计算逻辑。一个缺点是 ANSI SQL 只允许一个 select 语句,而 FugueSQL 允许多个。FugueSQL 允许将变量赋值为临时表,这是一种比通用表表达式(cte)更友好的语法。欲了解更多信息,请查看 FugueSQL 文档。
这个 FugueSQL 接口建立在抽象层之上,使它与 Pandas、Spark、Dask 和 BlazingSQL 兼容。它是一等公民,提供与 Fugue Python API 相同的灵活性和好处。
还有一个带有语法高亮的笔记本扩展,允许用户调用 %%fsql 单元格魔术。欲了解更多信息,请参阅本文。请注意,语法突出显示目前仅适用于经典的 Jupyter 笔记本,不适用于 JupyterLab。它在 Kaggle 环境中也工作得很好,利用了 Kaggle 内核中的多个内核。

作者图片:Jupyter 笔记本赋格扩展演示
分割
对于某些用例,是否有更好的方法来划分数据?
Spark 默认使用散列分区。对于少量的键,这很容易导致不均匀的分区。这看起来没什么大不了的,但是如果每个键需要一个小时来运行,那么不均衡的分区可能会使一个作业需要多花几个小时来运行。棘手的是,即使不编写大量代码,Spark 上的分区也无法实现。
Fugue 允许用户在默认散列分区、随机分区或均匀分区之间进行选择。这些划分策略中的每一种都非常适合不同的用例。下面是何时使用每种方法的表格摘要。

作者图片:赋格中不同的分割策略
均匀分区对于需要大量计算的较小数据特别有用。当数据不对称时,一些分区最终会比其他分区包含更多的数据。因此,执行时间取决于数据量最大的分区的完成时间。通过为每个分区强制相同数量的元素,可以减少执行时间。更多信息,请查看分区文档。
在下面的代码中,我们得到了包含最高值 col2 的五行。预排序在数据分区时应用。transform()函数也可以接受一个partition策略。
赋格上的分区操作示例
神游 vs 考拉 vs 摩丁

作者图片:考拉、摩丁、赋格
Fugue 经常被拿来与考拉和摩丁相比较,作为单核计算到分布式计算之间的桥梁。考拉是 Spark 的熊猫接口,摩丁是 Dask 和 Ray 的熊猫接口。很难比较这两个项目,因为目标不同,但主要的区别是这两个框架认为 Pandas 可以成为分布式计算的语法,而 Fugue 认为 native Python 和 SQL 应该是,但也支持 Pandas 的用法。
一开始,当来自熊猫时,转换到考拉或摩丁可能看起来容易得多。一些用户错误地期望熊猫import语句可以被修改,并且代码将在分布式设置上完美地工作。在很多情况下,这种承诺好得难以置信,因为这需要库的接口与 Pandas API 完全同步,而这几乎是不可能的。例如,滚动操作的的考拉实现没有熊猫 API 提供的窗口类型。
但是在分布式环境中,与 Pandas API 完全对等并不总是有意义的。例如,一个转置操作可以在 Pandas 中工作,但是当数据分布在不同的机器上时,它的开销非常大。在极端的情况下,应用程序必须做出极端的妥协才能让这个 import 语句发挥作用。如果一个操作在 Modin API 中不存在,架构默认使用熊猫,它将所有的数据收集到一台机器上。这很容易使收集所有数据的机器过载,这些数据以前分散在多个工作人员中。
神游避免使用 Pandas 作为分布式计算操作的语法也有哲学上的原因。考拉和摩丁在语法中增加了词汇,比如持久化和广播操作来控制工人之间数据移动。但是这里的错位是熊猫的基本语法不能很好地翻译成分布式场景。索引对熊猫的工作流程非常重要。在一个典型的脚本中,会使用大量的reset_index()和set_index()调用。执行 groupby 操作时,会自动设置索引。该索引保留了一个全局顺序,允许使用iloc方法。一些操作甚至在连接条件中使用索引。在分布式设置中,顺序是无法保证的,因为跟踪顺序通常会产生不必要的计算开销。
性能-生产率权衡和神游
在代码性能和开发人员生产力之间总是有一个折衷。性能优化需要深入的特定于引擎的技巧,这些技巧很难编码和维护。另一方面,优化开发人员的生产力意味着尽可能快地生产出解决方案,而不用担心代码性能。为了迭代速度和可维护性的显著提高,Fugue 牺牲了一点性能。通过专注于在分区级别定义逻辑,用户通常会发现他们的代码变得更加清晰,并且大数据问题变得小而易管理。
虽然在 Spark 上使用 Pandas 和自定义函数曾经较慢,但由于 Spark 引擎的改进(Apache Arrow 的使用),它的性能越来越好。Fugue 应用转换的效率损失非常小,用户经常看到在分布式设置中通过更有效地处理数据而获得的代码加速。事实上,Fugue 将许多代码转录为 Spark 代码,这意味着在许多情况下唯一改变的是界面。
结论
在本文中,我们讨论了使用 Spark 的难点,包括可测试性、与 Pandas 的不一致性以及缺乏健壮的 SQL 接口。我们展示了一个更友好的界面来使用 Spark。神游不与火花发动机竞争;神游使它更容易使用。通过使用 Fugue,用户通常会看到大数据项目更快的迭代,从而减少交付时间和项目成本。
使用神游是非侵入性的,没有任何依赖性。逻辑可以用原生 Python 代码或者熊猫来定义,然后移植到 Spark。 Fugue 相信适应用户,所以他们可以专注于定义他们的逻辑,而不是担心它的执行。尽管本文没有涉及,但 Fugue 也提供了使用本机 Spark 代码或 Spark 配置的方法。它不限制对底层框架的访问。
联系我们
如果你想了解更多关于赋格的知识,讨论你的 Spark 痛点,甚至纠正本文中提到的一些错误,我们很乐意听到你的意见!此外,如果您希望我们向您的团队、聚会或会议做演示,请随时联系我们。
- 邮箱: hello@fugue.ai
- 松弛:加入这里
资源
赋格的附加资源:
抽象层打开了许多更具体的应用程序。到目前为止,我们已经介绍了验证、调优和 SQL 接口。
冰淇淋介绍:不要再使用 Print()调试 Python 代码了
为什么我停止使用 print()语句进行调试,为什么您也应该这样做

照片由 bryn beatson 在 Unsplash 上拍摄
动机
编程时出错几乎是不可避免的。事实上,可以这么说,程序员花了大量的时间进行调试,以使他们的代码没有错误。
在调试时,使用print()语句来理解管道的流程并发现意外行为无疑是最广泛采用的方法。
然而,使用print()有许多注意事项,例如:
- Print 语句通常用于向用户显示输出。如果程序员使用
print()进行调试,在调试结束后,程序员应该注意只删除那些用于调试的特定print()语句。 - 通常,在调试过程中,您可能会一个接一个地打印多个变量。在这种情况下,程序员必须手动格式化输出以增强其可读性。
上面,我们打印了两个变量。虽然这里我们知道第一个变量是var_1,第二个是var_2,但是随着变量数量的增加,可能需要您在代码和输出之间来回查看,以找出哪个输出对应于哪个变量。
当然,我们可以打印更多的细节,如下所示,但这只是增加你的工作。
- 有时,程序员也可能对打印行号、函数名及其输入等感兴趣。,这增加了编写长/多
print()语句的复杂性。 - 在大多数情况下,代码库不仅限于一个文件。相反,有多个文件组成了管道。在这种情况下,人们可能会对在调试期间显示文件名感兴趣,这对于
print()来说可能是一个麻烦。
以上原因使得print(),至少对我来说,是调试的最差选择。
谢天谢地,Python 中有一个很好的选择。
介绍冰淇淋🍦!
冰淇淋
IceCream 是一个 Python 库,它用最少的代码使调试变得轻松易读。
它的流行特性包括打印表达式、变量名、函数名、行号、文件名等等——我们将在这篇博客中讨论。
安装冰淇淋
您可以使用pip安装icecream库。
进口冰淇淋
使用这个库的标准惯例是导入ic模块,如下所示:
冰淇淋入门
使用冰淇淋库就像打印语句一样简单。你需要把print()换成ic()。就是这样。
注意区别!ic()不仅打印值,还打印传递的变量名。
冰淇淋不仅仅是一个变量。相反,您可以在函数、类等上使用它。
多酷啊!它打印方法的名称(func)、传递的参数(3)和输出(6)。
在ic()方法中的每一个expression都会和expression的value一起打印出来,如下图所示。

冰淇淋打印表达式及其值(图片由作者提供)
使用 IceCream 进行调试也可以应用于常见的 Python 数据结构。下面显示了一个 Python 字典的示例。
检查执行情况
很多时候,程序员使用print()来显示有意义的(有时是随机的)语句,以确定程序的流程。如下所示:
冰淇淋也能让你摆脱那些奇怪的陈述。
只要叫ic()就完事了。它将打印文件名、行号和其他细节(如函数名,如果有的话)以及时间。简单。
在整个项目中使用冰淇淋
接下来,您可能想知道是否需要在每个 python 文件中导入库?当然不是!
为了使这些方法在所有项目文件中可用,从根文件中的icecream导入install模块,如下所示:
通过install,ic()在项目范围内可用。
添加自定义前缀
如果你注意到上面的话,ic()语句的输出以“ic|开始。这是冰淇淋提供的默认前缀。
但是,如果出于某种原因,您希望用自定义前缀替换它,您也可以这样做。这是通过在ic.configureOutput()方法中指定prefix参数来实现的,如下所示:
调试后删除冰淇淋语句
调试完代码后,您可能希望删除所有不必要的调试语句。
由于ic()语句在语法上不同于print(),您可以在您的编辑器中搜索模式“ic(,并删除这些语句,如下所示:

从代码中删除 ic()语句(Gif by author)
或者,您可以使用ic.disable()停止ic()打印。如果您希望再次使用它们,请使用ic.enable()。
结论
用print()语句进行调试是一种混乱且不优雅的方法。将输出映射到其对应的调试语句是令人困惑的。此外,它需要额外的手动格式化来理解输出。
如上所述,Python 中的冰淇淋库是一个很好的替代品。它用最少的代码使调试变得轻松易读。
点击阅读更多关于冰淇淋的信息。
感谢阅读!
🚀订阅数据科学每日剂量。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。
🧑💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。
✉️ 注册我的电子邮件列表 不要错过另一篇关于数据科学指南、技巧和提示、机器学习、SQL、Python 等的文章。Medium 会将我的下一篇文章直接发送到你的收件箱。
我喜欢探索、实验和撰写关于数据科学概念和工具的文章。你可以在 LinkedIn 上和我联系。
引入“思想凝聚”
原文:https://towardsdatascience.com/introducing-idea-condensation-5a0c1dbe5cca
自动识别在一系列相似文本语句中表达的“想法”的功能
介绍
现代计算时代给人类带来了广泛的挑战,无论是在技术行业还是在全球文化中都是如此。社交媒体平台、调查和公共研究公司、公共关系、广告和零售产生了大量的文本数据。在 Within3,我们管理洞察和沟通管理平台,促进制药公司和开药的医疗保健提供商之间的对话。管理和理解来自医疗保健提供者的大量自由文本反馈对于理解和管理意外问题至关重要,这些问题甚至可能在药物脱离临床试验并投入使用后出现。
在数据科学中,众所周知,文本数据比数字数据更难研究。不仅文本更加多变,充满了细微差别,并且依赖于上下文,而且对一个问题的正确答案会随着回答它的观众的不同而不同。当我们甚至不能总是就正确答案达成一致时,编写软件来分析文本数据并产生可靠且可操作的答案本身就存在问题。然而,理解大量的文本反馈对于组织确定业务决策的影响和识别盲点仍然是至关重要的。如果医疗保健提供者报告了某种特定药物在某种情况下的罕见副作用,生产该药物的制药公司越早解决他们研究管道中的问题,他们就能越早解决问题。如果一项调查正在征求对某个问题的文本回答,并收到数千个答案,那么拥有一个自动化的数据驱动的解决方案来识别常见答案和回答中的结构是至关重要的。如果购买某一品牌床单的顾客反复撰写公开的产品评论,说床单从包装中出来时有一股难闻的气味,制造商会想知道,而不需要有人在注意到之前手动阅读数千条评论。
用于分析文本数据的常见技术通常包括关键字识别、监督分类模型和向量嵌入模型(即“Word2Vec”和类似的方法),它们将向量分配给术语,这有助于对文本数据的向量表示进行代数操作。虽然这些可以引出一些关于文本数据集中的词汇和主题的有用信息,但它们都没有真正解决每个人真正想知道的问题:
"这些数据中表达的最常见的想法是什么?"
我们一直在开发一种方法来直接解决这个问题。这种过程的一个关键组成部分是从多个语句的文本数据输入开始,这些语句用不同的确切措词传达大致相同的“想法”(将在下面更精确地定义),并将它们“浓缩”成最佳地表示全部输入的单个输出语句。
作为一个介绍性的教学例子,如果我们有以下四个简单的关于一些咖啡的文本评论:
"这咖啡尝起来又苦又酸。"
"我不喜欢这咖啡有多苦。"
"这咖啡酸得令人不舒服。"
“味道太酸了。”
我们如何输出一个“答案”,比如
"这咖啡酸得让人不舒服"
哪一个最能代表输入中所表达的想法?
使用现有的“自然语言处理”(NLP)“词性标注”(POS tagging)和“依存句法分析”技术,我们引入了一种我们称之为“思想浓缩”的技术来执行该功能。在文本中表达的多个相似的想法被“浓缩”成一个单一的表达,这个表达最好地代表了所有的输入。
什么是“想法”?
自然语言处理领域包括用数学和统计技术研究语言数据;如果我们能把一个语言数据集翻译成数字形式,那么我们就能使用现有的定量分析技术来描述这些数据。在我们能够在本研究中说明什么是“想法”之前,我们需要说明 NLP 中允许我们检测文本数据中的结构的一些常见技术。
n-grams
英语 NLP 中的“术语”通常是一个单词(“the”、“cat”、“runs”、“away”等)。),但也可以指像“客厅”、“满月”、“波士顿环球报”(一份报纸)这样的复合词,甚至可以指“自然语言处理”(一个三字复合词)这个词本身。例如,“波士顿环球报”是一个单数名词,它指的是一份报纸,因此是除了“波士顿”(一个城市)和“环球报”(教室中地球的一种表示)之外的一个单独的“术语”。在 NLP 中,被识别的术语也经常被称为“n 元语法”,其中“n”指的是组成术语的单词的数量。“Cat”是由一个单词组成的术语,因此可以表示为“unigram”;“波士顿环球报”由两个单词组成,因此可以被表示为“二元模型”;类似地,“自然语言处理”是一个“三元模型”(我们很少考虑 n > 3 个术语)。
标记化
接下来,NLP 中的“标记化”以其最一般的形式,是获取一系列文本字符并将它们分割成一系列片段的过程,其中每个片段本身被表示为一个“标记”。“术语标记化”是一种更具体的情况,即获取一系列文本数据,并根据我们上面的定义将其分解成一系列单独的术语。二元模型和三元模型通常通过对包含它们的单词一起出现的计数统计来识别,并设置任意阈值。例如,假设我们正在考虑一个文档,其中术语“波士顿”和“全球”各出现五次,但每次出现都是在序列“波士顿全球”中,那么我们可能要考虑报纸“波士顿全球”的二元模型,而不是对城市或球形地图的引用。
词性标注
通过术语标记化,我们可以翻译字符串
"这只猫跑过波士顿环球报."
转换成一维数组
["the", "cat", "runs", "through", "the", "Boston Globe"]
现在,我们可以将一些文本数据标记成一系列术语。词性标注(POS tagging)是一种为描述其在句子中的功能的单个术语分配标签的技术。在我们刚刚标记化的句子中,“cat”将被标记为“名词”,“runs”被标记为“动词”,等等。因此,我们的术语标记化的文本数据序列可能看起来像这样
[("the", "determiner"), ("cat", "noun"), ("runs", "verb"), ("through", "preposition"), ("the", "determiner"), ("Boston Globe", "noun")]
在实践中,一个名词将被标记为“NN”而不是“名词”,一个动词将被标记为“VBZ”而不是“动词”,等等,但我们不需要在这里深入研究。我们将在本次研究中使用空间位置标签。
依存句法分析
对文本数据进行标记和词性标注后,我们接下来讨论文本数据中的术语如何相互关联。术语“跑”是由名词“猫”执行的动作。既然“猫”(名词)在执行动作“跑”(动词),那么“猫”就是动词“跑”的“主语”。动词“跑”要靠名词“猫”才有意义。动词不仅有实施动作的主语,还有实施动作的“宾语”。
请注意,“猫跑了”似乎只是勉强符合一个想法,因为它是反身性的——动词的主语和宾语都是“猫”。完整的想法是“猫在波士顿环球报上奔跑”。
动词“运行”也出现在这个想法的某个地方。跑步是通过“波士顿环球报”(介词的宾语)发生的。
注意,在英语中,“介词”和“后置”统称为“adpositions ”,因此介词“through”将在下面被指定为词性标记“ADP”。因为介词“Boston Globe”的宾语跟在 adposition“through”后面,所以更具体地说是一个“介词”。如果 adposition 的宾语在 adposition 之前,则为“后置”。
这句话的所有术语形成一个想法是因为它们之间有一个内在的关系结构。在计算语言学中,这组关系被称为“依赖树”。这里有一个的例子,展示了我们句子的依赖关系

(图片由作者创建)简单句的依存关系树图(包含短语)
识别这些关系的过程称为“依存解析”。我们将使用 SpaCy python 包的依赖解析器。
为 NLP 定义一个“想法”
有了这些技术,我们就能解决我们试图解决的问题。当一个客户带着一组文本数据来问我们“应答者告诉我们的主要思想是什么?”,我们必须首先问自己“NLP 中的‘想法’是什么?”。从 NLP 的角度来看,我们找不到任何关于“想法”的标准定义,所以我们提出了一个。
直觉上,一个“想法”需要不仅仅是任何一个术语。“猫”只是一个名词,“跑”是一个动作或动词;两者都不符合一个完全成形的想法的直觉概念。“猫跑”几乎像一个想法,而“猫跑”似乎像一个单一的,完全形成的思想的最低限度。像“白狗”(以及形容词和形容词描述的名词,没有任何动作)或“吃食物”(动词和动词作用的宾语名词,没有表现动词的主语名词)这样的词对也不太符合完整的概念。
然而,复合句直觉上似乎走得太远了。认为
"这只猫跑过波士顿环球报,然后他藏在一个盒子里."
这个句子仍然很短,但是包含了两个完整的想法:一个想法是一只动物跑过一个地方,另一个想法是同一只动物藏在一个更具体的地方。一般来说,句子太长,不能表达单一的意思。
因此,为了表示一个单独的想法,我们要寻找的不仅仅是单个术语,甚至是成对的术语,而是不仅仅是一个通常包含不止一个想法的完整句子。为了 NLP 的目的,我们决定将一个“想法”表示为一个英语从句,无论是从属还是独立的。子句是包含谓语(动词)和主语(名词)的语法单位。
因此,使用我们的术语标记器、词性标记器和依存解析器,我们可以从单个动词及其主语和宾语名词开始识别文本数据中的单数概念。这些名词和动词也可能有形容词或副词来描述或修饰它们,所以应该包括在内。此外,与动词相关的 adpositions,如我们上面的例子,也应该包括在内。一个“想法”将由一系列匹配这个模式的术语定义,使用刚刚描述的词性标注和依存解析。
引入“思想凝聚”
管理和研究文本数据的困难在于固有的模糊性。一个特定的想法可以用无数种不同的方式表达,每种表达方式都有细微的变化。根据我们对自然语言处理概念的定义,我们在这篇文章中提出了一个狭义的应用。如果我们有一系列 idea 大小的文本数据字符串作为输入,这些字符串都表达了一个相似的想法,只是在特定的措辞上有所不同——暂且撇开这一系列文本是如何获得的问题不谈——那么我们如何才能输出一个 idea 大小的文本字符串来最好地代表整个输入呢?
我们将展示一种方法来解决这个问题,这种方法只利用了词性标注、依存解析和结果的计数统计。请注意指导我们方法的一些原则:
- 应该避免全有或全无的方法。如果找不到完整的简单句描述,那么可以返回可识别的较少语法结构,即使它们不完全符合我们的 NLP 对“想法”的定义。从最高质量的可接受输出到最低质量的可接受输出有一个排序。
- 如果最高等级的质量输出有平局,则返回所有结果。
- 文本数据是有噪声的,因此任何试图解决这个问题的方法都需要对输入中的噪声具有弹性。
- 返回两种形式的输出:
- 1.可以从输入中自动识别的最高质量的语法结构
- 2.最能代表整个输入序列的单个输入字符串。
该方法从标注输入开始。然后,对于每个标记的词性,识别最常见的术语。也就是最常见的名词,最常见的动词等。,被识别为输入。
初始示例
让我们用一个简单的、人为的例子来介绍我们的想法浓缩方法。假设我们的输入由以下四个简单的句子组成。所有四个句子说的东西大致相似,但在措辞和意思上略有不同。
"The red fox jumps over the lazy dog."
"The red fox jumps."
"The fox jumps. "
"The fox jumps over the dog."
想法浓缩中的词性标注
我们的 POS 标签检测以下计数

(图片由作者创作)
我们从词类标签中注意到每个词类最常见的术语

(图片由作者创作)
思想凝结中的依存解析
现在我们已经有了一些不同词类的最常用术语,让我们看看是否可以用依存解析自动检测它们之间的任何关系。一般来说,这样的关系不保证能找到。如果输入由具有不同术语的完全不同的语句组成,那么依存句法分析就不太可能为每个识别出的词类找到最常见术语之间的共同关系。然而,输入语句越相似,我们就越有可能找到不同词类的不同最常见术语之间的共同关系,从而允许我们自动构建关于输入整体的更复杂的概念大小的语句。因此,我们需要明确,这种思想浓缩方法是基于这样一个假设,即已经做了工作来确保输入是相似的语句。想法浓缩将是在文本数据集中总结想法的更大方法论的一个组成部分。
让我们看一下第一个例子中四个输入中每一个的依赖树的图形表示。
"红色的狐狸跳过了懒惰的狗."

(图片由作者创作)“赤狐跳过懒狗。”
“赤狐跳。”

(图片由作者创作)“赤狐跳。”
“狐狸跳。”

(图片由作者创作)“狐狸跳。”
"狐狸跳过了狗。"

(图片由作者创作)“狐狸跳过狗。”
指向感兴趣的术语的依赖性在该术语的“头部”。远离我们感兴趣的术语的依赖项在术语的“子术语”中。从最常见的名词“fox”开始,指向“fox”的最常见的依赖项是动词“jumps”。所以,术语“jumps”是最常见的名词“fox”的“head”中最常见的术语,但“jumps”恰好也是最常见的动词。因此,我们已经确定了描述整个输入的术语之间的一种关系。“fox jumps”是我们可以根据以下事实自动构造的结构:(1)“fox”是输入的最常见名词;(2)“跳转”是最常见的输入动词;而(3)最常见的动词“跳”是最常见的名词“狐狸”的头部最常见的术语。
让我们继续看最常见名词“狐狸”的孩子。限定词“The”在“狐狸”的子女中出现了两次,形容词“red”也在“狐狸”的子女中出现了两次。术语“红色”也恰好是最常见的形容词。因此,描述“狐狸”的“红色”也应该被认为是对整个输入的描述,我们可以构造“红色的狐狸”或“狐狸是红色的”。
最后,将两个结果放在一起,因为最常见的名词“fox”在其头部有最常见的动词“jumps ”,在其子代中有最常见的形容词“red ”,所以我们可以自动构造
《赤狐跳跃》
作为一个想法大小的语句,最好地代表了四个输入语句的整体。换句话说,我们将四个想法“浓缩”成一个表示输入数据的想法。(输出想法恰好与输入语句之一完全匹配,这是巧合)。
在我们的思想浓缩方法中,我们不想仅仅依赖自动构造。我们还想确定哪个特定的输入语句最能代表整个输入,然后返回那个例子。
此时,我们通过返回包含不同词类的最常见术语的单个输入语句来实现这一点。在这个例子中,如果我们只考虑最常见的名词(“fox”)、动词(“jumps”)、形容词(“red”)和副词(未标识),那么我们想要标识哪些(如果有的话)输入包含所有的“fox”、“jumps”和“red”。在这四个输入中,有两个实际上包含了所有这三项
"红色的狐狸跳过了懒惰的狗."
“赤狐跳。”
因为这两者之间有联系,所以两者都被返回。
这个例子的最终输出是,
最佳答案:
《赤狐跳跃》
输入的最佳代表范例:
"红色的狐狸跳过了懒惰的狗."
“赤狐跳。”
第二个例子
让我们先看一个包含少量输入语句的构建示例,然后再看更多实际的示例。
"The angry baboon viciously bites the poacher."
"The baboon is angry and really fighting hard."
"Now the baboon is running away."
这个例子有更多的词类,因为有副词,而且语句之间不太相似。我们在上面提到过,指导我们开发方法的原则之一是,我们不想要一个全有或全无的方法。这三种说法都提到了一只“狒狒”,但是在这三种说法中,每一种说法都发生了不同的行为。因此,我们可能无法从输入中构建一个完整的 idea 大小的语句,但是我们也不想什么都不返回。应该返回可以识别的整个输入的最佳表示。主观上,我们可以看到对狒狒的描述对于所有的输入都是相同的,即使相关的动作是不同的。因此,我们的方法应该至少能够返回一些关于输入的公共元素的信息。
让我们看看我们的方法能做什么。我们的 POS 标签识别

(图片由作者创作)
不同词类最常见的术语是

(图片由作者创作)
现在让我们看一下三个输入中每一个的依赖解析。
"愤怒的狒狒恶毒地咬着偷猎者."

(图片由作者创建)“愤怒的狒狒恶毒地咬着偷猎者。”
“狒狒生气了,真的在拼命打。”

(图片由作者创作)“狒狒生气了,真的在拼命打。”
“现在狒狒正在逃跑。”

(图片由作者创作)“现在狒狒正在逃跑。”
再次从最常见的名词“狒狒”开始,“狒狒”头部最常见的术语有三种说法:“咬”、“是”和“跑”。我们应该注意到,虽然“is”是最常见的动词,但“is”只有一种用法是称赞“baboon”(第二个输入),而另一种用法是称赞“running”(第三个示例),这就是为什么“is”与“bites”和“running”并列。
在这一关系的三个术语中,“是”也是最常见的动词,因此我们可以将“狒狒”与“是”联系起来,构成“狒狒是”。
最常见的形容词是“愤怒的”,愤怒的头部最常见的术语是“是”(最常见的动词)以及“狒狒”(最常见的名词)。所以,我们也可以把形容词“生气”和“狒狒”联系起来。
我们的最佳构造输出是
“狒狒生气了”。
它包含了陈述中常见的部分,同时忽略了输入中不重复的无关部分。
对于最佳代表性输入,只有一个包含最常见的名词、最常见的动词、最常见的形容词和最常见的副词之一:
“狒狒生气了,真的在拼命打。”
这个例子的最终输出是,
最佳答案:
“狒狒生气了”。
输入的最佳代表范例:
“狒狒生气了,真的在拼命打。”
难以下咽的苦咖啡
在第三个例子中,我们想说明这种方法如何对输入中的噪声具有鲁棒性,并进一步展示构造的输出如何不限于形成单个 idea 大小的文本语句。考虑一个由以下关于咖啡的语句组成的输入数据集,其中混合了一些不相关的语句。
"The coffee tastes excessively bitter and acidic."
"I do not like how bitter the coffee is."
"This coffee is unpleasantly acidic."
"The taste is too acidic."
"I am uselessly commenting some other unrelated statement."
"This is another noise comment."
词性标注和依存句法分析发现

(图片由作者创作)
我们的自动输出结构实际上寻找各种各样的可能的语法结构,并按照优先顺序输出。最完整和最彻底的“想法”被给予最高的优先权,而简单的结构,像仅仅是一个名词和一个描述它的形容词,被给予较低的优先权。
对于此输入,最能描述整个输入的完整构造输出为:

(图片由作者创作)
同时,输入中被检测为最好地描述全部输入的一个语句是:
"这咖啡酸得令人不舒服。"
即使输入中存在外来噪声,两种形式的输出也如此接近,这一事实证明了我们提出的思想凝聚方法的力量和有效性。
一口新鲜咖啡
在我们的第四个例子中,我们构建了一个数据集,该数据集由一个面包师的十几个句子组成,这些句子是从亚马逊上一个实际咖啡产品的评论中复制并粘贴而来的。虽然我们没有像前三个例子那样直接出于教学目的来写这些陈述,但我们确实挑选了主要与咖啡味道有关的单句陈述。在实践中,人们甚至经常写不出完整的句子或接近正确的语法,因此拥有一种对不太理想的情况有弹性的方法是至关重要的。
"This coffee has a really pleasant aroma."
"This one really smelled great."
"Great smell and taste!"
"I picked up on some great aromatics when I first bought this."
"Morning brews have a lighter scent."
"It really emanates an evanescent smell that fills my house and company loves."
"I love waking up to the smell of this coffee."
"Freshness and that wonderful aroma is great!"
"Love this coffee!"
"Great bold smoky, almost chocolaty taste with sweet aromas and no sourness."
"The crema is magnificent. It has a velvety flavor and nice aroma."
"The aroma, flavor, and smoothness is unmatched."
"Best espresso beans ever!"
词性标注和依存句法分析发现

(图片由作者创作)
思想浓缩的构造输出是

(图片由作者创作)
最能代表整个输入数据集的特定输入为(并列第一):
"我第一次买这个的时候,发现了一些很棒的芳香剂."
"新鲜和美妙的香气是伟大的!"
最后一个例子
在我们的最后一个例子中,让我们考虑一个不是人为的输入。虽然我们已经明确地提出了思想浓缩适用的假设,但我们还没有提出在实践中如何以及何时使用它的更广泛的背景。
在很多情况下,我们会发现自己的数据集由许多段落长度的文本回复组成,这些回复是人们针对某个共同话题而写的。它们可以是对特定调查问题的回答,也可以是对特定产品或服务的评论。每个人都想知道答案的问题是“在这个数据集中,人们向我们表达的最常见的想法是什么?”
关键词和分类模型不回答这个问题。因此,假设我们对答案一无所知,并希望找到我们无法预测的答案,我们试图回答上述问题,并寻找产生输入数据集结构固有的“想法”(如本文上文所定义)的解决方案。这就是为什么我们开始采用无人监督的训练方法。无监督训练技术旨在识别数据集固有的结构。当应用于文本数据时,无监督训练方法可以识别数据集中具有相似措辞的聚类。
咖啡和床单
本着这种精神,我们构建了一个从亚马逊复制的 500 个段落长度的用户评论的数据集。250 条评论与特定品牌的咖啡有关,250 条评论与特定品牌的床单有关。70%的咖啡评论在情感上是正面的,并且与咖啡的气味有关,而另外 30%是关于咖啡的酸度/苦味的负面情感评论。70%的床单评论是关于床单舒适的正面情绪评论,而另外 30%是关于床单从包装中散发出难闻气味的负面情绪评论。
第一步是将数据集标记为子句,因为我们在上面决定使用子句和语法上等同于文本中的单数“idea”。接下来,我们使用术语频率-逆文档频率(TF-IDF)模型来创建每个子句的向量表示。有了这些向量表示,我们就可以对数据集执行 K-means 聚类。作为 K-means 聚类过程的一部分,我们使用我们的划分方法首先估计数据集固有的最佳聚类数,然后在假设自动检测到最佳聚类数的情况下运行 K-means 聚类。通过将数据集划分成这 K 个聚类,我们可以将对应于每个聚类的文本视为思想浓缩的输入。
包装上散发着怪异气味的床单
这个想法浓缩的输入是从我们的咖啡和床单开发数据集自动生成的——它是通过自动化 K-means 聚类过程生成的一个聚类的文本。
"i love the smell first thing in the morning"
"smell"
"horribly overburned taste and smell"
"smell and taste good"
"it is light brown in color and you can smell the acid as soon as you open the bag"
"my wife said smell good"
"smell great"
"they smell weird"
"they smell a bit inky after coming out of the package"
"also they had a slight ‘new smell’ kind of a bad smell"
"strong chemical smell"
"they feel weird and smell a bit like plastic when you take them out of the package"
"they smell weird"
"great smell and taste"
"strong chemical smell"
"after owning them for less than a year they have developed a musty smell"
"weird chemical smell made us choke"
"i live in southwest florida they came with really weird smell"
"it smelled like cat pee when it arrived and i had to wash it 3 times to remove the awful smell"
"the smell was still"
"there was a strong chemical smell when the package was opened (took washing them 3 times to get smell out)"
"they smell like they’ve been in my grandma’s linen closet for 20 years"
"musty smell"
"the smell though"
"they smell terrible"
"with fabric softener and unstoppables and can not get the smell out of them"
"they smell like a combination of antifreeze and mothballs"
"i noticed quite a bit of blotchy discoloration and a strange burnt smell"
"strong chemical smell"
"horrible chemical smell even after washing"
"the terrible chemical smell is still present"
"i'm going to wash them and see if the smell goes away"
"both had this weird smell that took several washes to get rid off"
"they smell like elmer's glue"
"they smell like something rotten"
"muted chemical smell (like an garage at a gas station)"
"they smell terrible"
"when i opened it they smell like perfume/scented detergent and are stained"
"they started to smell"
"rating is 3 stars due to the chemical smell after 2 washes in the past 24 hours after receiving"
"i can’t get the burnt smell out of my dryer and almost burned my condo down"
"terrible petroleum smell out of the package"
"fishy smell from the material even after multiple washings"
"hard to wash off the “new” funky smell"
"these smell like paint"
"the chemical smell was super strong when i took them out of the package"
"they smell heavily like chemicals"
"they smell bad (like petrol)"
"very weird smell"
"they still smell strongly of formaldehyde"
"they came with a really weird smell"
"when i opened i felt a strong smell and after wash the smell didn’t away completely"
"don’t really ever smell clean"
"weird smell"
"i'm not sure what's going on with the smell"
"these have a terrible smell right out of packaging"
"the smell was awful"
"they smell horrible"
"they smell like you just painted a room and it needs to dry"
"i’ve washed them multiple times to try and get the smell out"
"it’s definitely an overwhelming smell"
"the smell really makes my mouth water like pavlov's dog just before my first sip"
"it really emanates an evanescent smell that fills my house and company loves"
找到了词性标注和依存关系分析

(图片由作者创作)
思想浓缩的构造输出是

(图片由作者创作)
最能代表整个输入数据集的特定输入为(并列第一):
"我住在佛罗里达州西南部,它们带着很奇怪的味道."
“它们带着一股非常奇怪的味道。”
非常舒适的床单
让我们尝试从同一个数据集和同一次 K-means 聚类执行中识别出的另一组想法。群集的 idea 大小的文本以及 idea condensation 的输入是:
"these sheets are soft & comfortable to sleep on."
"these are honestly the most comfortable sheets i’ve ever owned."
"the sheets are pretty soft after a few washes and are very comfortable."
"these sheets are lightweight which makes them very comfortable for me."
"both the pillowcases and sheets are soft and very comfortable."
"these sheets are the most comfortable that i've ever slept in."
"the sheets are very comfortable."
"they are comfortable sheets that can withstand summer heat."
"we asked our guest what they thought of the sheets and they said they were very comfortable and soft."
"the sheets and pillow cases are comfortable and i like them a lot."
"now i have these and they are by far the most comfortable sheets i have had in a while."
"these are the sixth set of amazon basics bed sheets i've purchased and they are very comfortable."
"these sheets are soft and comfortable without becoming oppressively hot."
"these sheets are soft and comfortable."
"i was very pleasantly surprised at how comfortable and soft these sheets are."
"these are among the most comfortable sheets i've ever slept on."
"however the feel of these sheets even after washing a few times is definitely comfortable."
"bought these for my guest room for my sister when she is over and they are quite possibly the most comfortable sheets ever."
"the sheets remain comfortable and smooth."
"comfortable sheets in great colors."
"comfortable sheets that will keep you cool at night."
"these sheets are better than the 400 thread count sheets that i have gotten in the past in an effort to be more comfortable sleeping."
"these sheets are extremely soft and comfortable."
"these sheets are soft and comfortable."
"super soft and light weight microfiber sheets are the most comfortable i have ever slept on."
"if you’re looking for comfortable high quality sheets at an affordable price then these are the sheets for you."
"the sheets got softer after the first wash and are very comfortable to sleep on."
"i get really warm when i sleep and these sheets are really lightweight which is comfortable for me."
"very soft and comfortable sheets."
"soft and very comfortable i have gone through a bimbos different sets of sheets and these by far are the ones i love."
"the sheets are super comfortable."
"they're actually very soft sheets and comfortable to sleep on."
"i got these sheets with minimal expectations and was blown away by how comfortable they are."
"these sheets are comfortable and soft."
"these are the most comfortable sheets or fabric for that matter that you will ever experience."
"comfortable sheets."
找到了词性标注和依存关系分析

(图片由作者创作)
思想浓缩的构造输出是

(图片由作者创作)
最能代表整个输入数据集的特定输入为(第一名出现了 7 次平局):
"洗过几次后,床单非常柔软,非常舒适."
"这些床单很轻,我穿着很舒服。"
"枕套和床单都很柔软,非常舒适."
“床单很舒服。”
“这是我购买的第六套亚马逊基础床单,它们非常舒适。”
"我对这些床单的舒适和柔软感到非常惊喜。"
"第一次洗后,床单变得柔软了,睡在上面很舒服。"
光滑的咖啡豆
虽然我们没有展示在我们的咖啡和床单数据集中发现的每个聚类的 idea condensation 结果,但让我们以一个例子来说明 idea condensation 不是一个昙花一现的奇迹。最后一个例子中给出的所有结果都对应于我们的自动文本聚类算法的一次执行。
这是来自另一个聚类的文本数据,该聚类似乎以咖啡的术语“smooth”为中心。这一组文本不像前两个文本那样被高度填充,因此最常见的名词有 16 种排列方式,每种排列方式只出现一次。尽管文本数据环境相对嘈杂,但仍能找到合理的表示。
"it’s very smooth.
"it is very smooth and his tummy tolerates more than one cup a day."
"and for me personally really went against what i'm looking for in an espresso - smooth."
"high acidity and not smooth at all."
"very acidic and not smooth."
"it's smooth."
"it is a well balanced bean that finishes smooth."
"creamy) and extremely smooth."
"smooth."
"they're smooth."
"it is very smooth."
"they’re smooth."
"than the smooth full body i am accustomed to with medium and dark roast guatamalan."
"get silky smooth and cleaned up."
"smooth texture with good amount of oils."
"silky smooth."
"it’s very smooth and flavorful."
"very smooth."
"smooth mouth-feel."
"smooth."
找到了词性标注和依存关系分析

(图片由作者创作)
思想浓缩的构造输出是

(图片由作者创作)
最能代表整个输入数据集的特定输入是:
"它非常光滑,他的肚子每天能忍受不止一杯."
因此,我们能够从真实用户评论的数据集开始——尽管我们在其中插入了一些我们知道希望我们的建模方法能够重现的想法——并自动生成一个想法大小的文本,准确反映该数据集中的特定聚类。我们的方法中没有一个部分知道任何关于咖啡、床单、任何东西的香味或任何其他特定数据集的内容。尽管如此,我们现在能够准确地识别和输出在大型段落长度文本数据集中表达的最常见的想法,作为一组简短、可消化的要点和例子——并且以完全自动化的方式这样做。
结论
我们已经展示了一项我们开发的技术,称之为“思想浓缩”。这种技术有效的假设是,我们有一个由一系列子句长度的文本组成的输入,这些文本表达大致相似的思想。使用所提出的方法,可以生成自动的子句长度文本输出,其最好地代表了输入数据中所表达的思想。此外,来自一系列输入数据的最能代表整个输入的特定条目也被识别和返回。从某种意义上说,我们正在将输入中表达的“想法”“浓缩”成一个最能概括输入的单一表达式。该方法完全不知道输入数据的实际内容。既不需要模型训练,也不需要选择分类方案。
想法浓缩是作为一个更大的文本处理方法的一个组成部分开发的,我们已经开发并表示为“想法摘要”。创意摘要的前提是,我们从任何任意文本数据集开始,假设它由大量条目组成,这些条目的长度大致相当于段落长度,而不是输入像小说这样的单一作品并对其进行摘要(创意摘要不是为处理这种情况而设计的)。想法摘要识别数据集中的想法簇,然后使用想法浓缩输出在原始文本数据集中重复的想法的简短列表,以及有代表性的示例选择。如果有必要,还可以更深入地研究与特定的已识别想法相关联的原始文本数据。通过开发这种方法,我们使组织能够更有效地解释大型文本数据集,并识别可操作的情报。
ML 性能跟踪简介
原文:https://towardsdatascience.com/introducing-ml-performance-tracing-4a1f0042b65d

ML 故障排除的演变:作者图片
ML 故障排除系列的第二部分
在本内容系列的第一部分中,我们介绍了当今 ML 故障排除是多么痛苦,以及如何开始 ML 监控。然而,仅仅监测并不能解决问题。
为了说明这一点,让我们回顾一下本系列第一部分中的情况。您正在享受早晨的咖啡,但这一次您实施了性能监控。你得到的不是产品经理的投诉,而是一个寻呼机工作警报,说“欺诈模型性能下降。”
您的产品经理、客户支持团队和客户仍然幸运地没有意识到欺诈交易的增加,并且您在问题对公司产生重大影响之前就意识到了它。性能指标已经超过了阈值,您看到了红灯— 但是现在该怎么办?为了查明并解决问题,需要 ML 可观测性。

性能指标超过了阈值—现在该怎么办?图片作者:艾瑞泽·艾
可观察性的关键组成部分
在基础设施和系统中,日志、度量和跟踪都是实现可观察性的关键。这些组件对于实现 ML 可观察性 也是至关重要的,这是一种深入理解模型在其生命周期中的数据和性能的实践。

最大似然可观测性的关键组成部分,系统可观测性
定义最大似然可观测性的关键组成部分
推理存储 —从模型中记录的 ML 预测事件的记录。这些是原始预测事件,包含有关模型预测的粒度信息。系统可观测性日志的含义和 ML 可观测性中的推理库之间有一些关键区别。这将包括在即将到来的职位!
模型指标 —预测事件的计算指标,用于确定模型随时间的整体健康状况,包括漂移、性能和数据质量指标。然后可以监控这些指标。
ML 性能跟踪 —虽然日志和指标可能足以理解单个事件或聚合指标,但它们很少在调试模型性能时提供有用的信息。为了解决模型性能问题,您需要另一种叫做 ML 性能跟踪的可观察性技术。
在本文中,我们将深入探讨如何使用 ML 性能跟踪进行根本原因分析。

比较系统可观测性和机器学习可观测性(Arize AI)
ML 性能跟踪简介
💡定义:什么是 ML 性能跟踪? ML 性能跟踪是一种方法,用于查明模型性能问题的来源,并映射回导致该问题的底层数据问题。
在基础设施可观察性中,跟踪表示请求或动作在分布式系统的所有不同节点间移动的整个过程。在 ML 可观测性中,轨迹表示模型在数据集和各个切片中的性能。它还可以通过多个依赖模型跟踪模型的性能,以找出导致性能下降的子模型的根本原因。当今行业中的大多数团队都是单模型系统,但是我们看到了一组越来越多的模型依赖链。
在基础设施和 ML 可观察性中,通过分析跟踪数据,您和您的团队可以测量整体系统健康状况,查明瓶颈,更快地识别和解决问题,并优先考虑高价值领域以进行优化和改进。
让我们深入研究 ML 性能跟踪工作流。它遵循三个核心步骤:
- 步骤 1:与另一个数据集进行比较;
- 第二步:按切片进行性能分解;
- 步骤 3:根本原因和解决方案。

机器学习性能跟踪工作流(Arize AI)
第一步:和你知道的东西比较
模型性能只有在与某些事情相关时才有意义——如果触发了警报,那么一定是某些事情发生了变化。
机器学习模型依赖于数据和代码。其中一个必须保持不变,以便在更改另一个时进行比较。所以你要么在多个数据集上比较同一个模型,要么在同一个数据集上比较多个模型。
机器学习的故障排除需要跨数据集进行比较。

跨数据集比较性能(Arize AI)
我们需要比较哪些数据集?
- 训练数据。您的模型必须经过某种训练,您可以在训练数据集和您在生产中看到的数据之间寻找差异。例如,也许一个欺诈检测模型在生产中出现了问题。您可以提取原始的训练数据集,并查看自那时以来假阴性百分比是如何变化的。
- 验证数据。对模型进行训练后,您应该在验证数据集上对其进行评估,以了解模型如何处理训练中未发现的数据。与您验证模型时相比,您的模型现在的性能如何?
- 生产中的另一个时间窗口。如果您的模型上周投入生产,但警报没有触发,那么从那时起发生了什么变化?
您还可以将您的模型的性能与您在生产中使用的以前的模型进行比较。例如,上个月的模型可能会为您的食品配送提供更准确的 eta。
第二步:超越平均值,分析切片的性能
💡定义:什么是切片?
数据集切片标识了您的数据的一个子集,该子集可能在性质上与其余部分不同。例如,从机场接站的拼车客户可能与“普通”乘客有很大不同。
在整个数据集中比较一个指标很快,但平均值往往掩盖了有趣的见解。大多数情况下,您看到的是一小部分,比如数据子集的一个子集。如果你能找到正确的切片,那么解决问题就变得微不足道了。理想情况下,这不应该涉及成百上千的 SQL 查询,因为您应该能够快速缩小选择范围。
例如,如果您看到欺诈检测模型的整个生产数据集的性能略有异常,它可能不会告诉您太多信息。另一方面,如果你在加州的交易中看到的份额更小,表现明显更差,这可能有助于你确定发生了什么。更好的是:如果你将范围缩小到加利福尼亚、某个特定的商户类别和某个特定的商户,并发现所有或大多数交易都是欺诈性的,这可能会帮助你在几分钟内而不是几天内确定原因。
真正的洞察力往往存在于几层之下。

作者图片(阿里泽·艾)
您希望能够快速确定是什么拉低了您的整体绩效。您想知道您的模型相对于您的比较数据集在不同部分的表现如何。
然而,由于片段的可能组合的数量呈指数级增长,这种需求变得复杂。您可能有数千个特性,每个特性都有几十个类别,一个切片可以包含任意数量的特性。那么你如何找到重要的呢?

作者图片(阿里泽·艾)
为了实现自动化,您需要某种方法来对哪些部分对您所看到的问题影响最大进行排序。如果存在这样的排名,你可以利用计算能力来处理所有可能的组合,并对每个部分的贡献进行排序。
简介:绩效影响得分💡
性能影响分数是衡量你的兴趣指标与平均水平相比有多差的指标(了解更多关于性能影响分数背后的数学 ) 。
理想情况下,ML 工程师应该一眼就能看出问题出在哪里。良好的可视化和简单的导航可以使这个过程非常直观,并帮助工程师专注于提供洞察力——这是人类最擅长的工作。
继续以欺诈模型为例,按性能影响分数排序使您能够缩小范围—在本例中,是加利福尼亚州一家名为“scammeds.com”的特定商家—与平均值相比,性能下降了 30%。由于在训练中没有此切片的数据,这可能表明需要重新训练模型或恢复到不同的版本。

性能切片:作者提供的图片(Arize AI)
将特征“merchant_name”按准确度和交易量进行分类,进一步揭示了“scammeds.com”在生产中的模型准确度仅为 5%,因此它拖累了整体性能,尽管它仅占整体交易量的一小部分(~15%)。

准确性分类、按特征分类的数量:按作者分类的图像(Arize AI)
可解释性如何融入 ML 可观察性?
可解释性 在机器学习中是指特征对一个预测的重要性。一些特征可能比其他特征对预测欺诈有更大的影响。将可解释性视为分割的圣杯是很诱人的,但是在这样做的时候你必须小心。
可解释性是解决问题之旅的起点,而不是终点。正如 Chip Huyen 提到的,可解释性帮助你理解你的模型如何工作背后最重要的因素。另一方面,可观察性帮助你理解你的整个系统。可观察性包括可解释性和其他几个概念。
使用功能重要性可以帮助您对故障排除的位置进行排序和优先排序。
回到欺诈模型的例子,可解释性可以说明问题出在哪里。如果您按对模型最重要的功能进行排序,您将很快发现功能州(如加利福尼亚州)和商户名称(如 scammeds.com)对于进一步研究发现潜在的性能问题非常重要。
虽然可解释性是一个极好的工具,但它不应该被用作解决模型问题的灵丹妙药。性能影响分数通过描述哪个部分对性能下降的原因影响最大来提供更多信息。
步骤 3:根本原因和解决方法
你揭开了大海捞针,找到了模型做得不好的地方。恭喜你!现在,让我们来回答更难的问题——为什么?
以下是模型性能下降的三个最常见的原因:
- 一个或多个功能存在数据质量问题;
- 一个更多的特性已经漂移,或者在生产中看到意想不到的价值;或者
- 有标签问题。
让我们更详细地看看这些。
1。一个(或多个)功能有数据质量问题
示例:您正试图找出为什么拼车应用程序的 eta 是错误的,并且您发现功能“上车位置”总是与实际上车位置相差 0.5 英里。
推荐的解决方案:数据工程团队需要经历“拾取位置”特性的生命周期,并找出它在哪里被破坏。当他们发现问题并能够实现功能修复时,应该会改进 ETAs。
2.一个(或多个)功能已经漂移,或者在生产中出现意外值
示例:您看到您的模型出现欺诈交易高峰,但您的模型没有发现它们。换句话说,假阴性增加了。这是来自一个特定的商家 ID(这是一个发送到您的模型的功能)。你最近也收到了来自这个商家 ID 的巨大的峰值。您应该会看到这个商家 ID 特性有所变化,显示您看到来自该商家的交易比以前多。
推荐的解决方案:在这种情况下,您想知道自您构建模型以来或者自性能下降之前,什么特性发生了变化。你要找到商家 ID 分布漂移的根本原因。在那之后,你可能需要重新训练你的模型,对你以前没见过的新商家 ID 进行上采样。在某些情况下,您甚至可能想要为这个用例训练另一个模型。
3.有标签问题
例子:一个预测房价的模型显示了一个特定邮政编码的价格分布的极端差异。邮政编码非常重要。进一步检查后,您发现训练数据显示该邮政编码被标注了两个不同的城市名称,例如 Valley Village 和 North Hollywood(城市名称“Hollywood”产生更高的房价)。
推荐解决方案:向标签提供商强调问题,并在标签文件中提供说明。
结论
在本系列的第一部分中,我们讨论了从无监控到监控的最初转变。这里,我们讨论了下一步:带有 ML 性能跟踪的全栈 ML 可观测性。
概括地说,ML 性能跟踪的基础是:
- 与你知道的东西相比;
- 超越平均值,进入数据切片;和
- 根本原因和解决方法。
现实生活中的复杂性通常意味着最小部分的错误都会导致经济价值的巨大损失。如今,这意味着 ML 工程师必须花费大量时间编写 SQL 查询,并手动剖析模型,直到解决方案出现。还有一种将可解释性视为捷径的自然倾向。虽然可解释性通常可以帮助您理解问题,但在您的武器库中拥有其他工具也很重要——尤其是性能跟踪——以找到问题的根源。
知道寻找什么样的信息,并拥有快速呈现信息的好工具——以一种容易理解的方式——可以节省许多时间、金钱和客户关系。
联系我们
如果这个博客引起了你的注意,并且你渴望了解更多关于机器学习可观察性和模型监控,请查看我们其他的博客和 ML 监控上的资源!如果您有兴趣加入一个有趣的 rockstar 工程团队来帮助模型成功生产,请随时联系我们,注册一个免费帐户,或者在这里找到我们的空缺职位!
介绍 Pandarallel:再也不要在熊猫身上使用 Apply 方法
为什么我在 Pandas 中停止使用 Apply(),为什么你也应该这样做。

照片由 Alain Pham 在 Unsplash 上拍摄
Pandas 库具有直观、优雅、初学者友好的 API,是 Python 中最好的表格数据处理库之一。
如今,几乎所有处理表格数据集的数据科学家都求助于熊猫来完成各种数据科学任务。
虽然 API 提供了圆滑的设计和广泛的功能,但是有许多限制使得 Pandas 在一些数据驱动的情况下不适用(或者效率低)。
我在下面的博客中谈到了五个令人担忧的局限性:
</5-things-i-wish-the-pandas-library-could-do-e9017c127779>
综上所述,Pandas 几乎所有的限制都源于其单核计算框架。
换句话说,即使您的 CPU 有多个可用(或空闲)的内核,Pandas 也总是依赖于单个内核,这抑制了它的性能。

熊猫的单核计算框架(图片由作者提供)
有人可能想知道我们什么时候能在熊猫身上看到这些更新。但老实说,我们没有任何线索,因为没有任何来自熊猫的官方更新。
目前,严酷的现实是大多数用户照原样使用熊猫,即效率低下。
令人欣慰的是,在过去几年中,熊猫以外的许多开发者已经开始着手这个问题。
因此,我们今天有一些工具可供我们使用,让我们利用熊猫的多核,使它更有效。
我将在这篇文章中讨论的一个工具就是panda parallel!
我们开始吧🚀!
介绍 Pandarallel(熊猫+水货)
Pandarallel 是一个开源的 python 库,允许你将 Pandas 的操作并行化到所有可用的 CPU 内核上。这可以通过修改一行代码来实现。

Pandarallel 的多核计算框架(图片由作者提供)
此外,pandarallel还提供了很酷的进度条(就像我们用 tqdm 得到的一样)来估计剩余的计算量。
目前支持的方法有apply()、applymap()、groupby()、map()和rolling()。
安装 Pandarallel
您可以使用以下命令通过pip安装 Pandarallel:
导入 Pandarallel
下一步是导入库并初始化它:
正如在输出中看到的,initialize()设置了执行我们的作业的工人数量,在本例中是4。
你可以在这里阅读更多关于初始化的内容。
此外,你还应该导入熊猫图书馆。
熊猫到熊猫
如上所述,从 Pandas 到 Pandarallel 的转换非常简单,只需要修改一行代码。
其中一些如下所示:

熊猫行动到潘达拉尔(图片由作者提供)。更多阅读:https://nalepae.github.io/pandarallel/
关键是把apply换成parallel_apply,把map换成parallel_map。
实验
接下来我们来对比一下熊猫的apply()和 Pandarallel 的parallel_apply()的表现。

Apply() vs Parallel_apply()(图片由作者提供)
由于 Pandarallel 利用了所有可用的内核,理想情况下,我们应该期望后者的性能优于前者。
让我们看看这是不是真的。
实验装置
在这个实验中,我创建了一个包含可变行和五列的虚拟数据帧。
更具体地说,我改变了从1 Million到10 Million的行数,并绘制了apply()和parallel_apply()的性能图。
这个实验中的函数定义如下,它返回一行的和:
为了消除随机性,我用特定的行数重复了十次的实验。
这个实验的代码如下所示:
结果
接下来,我们来看结果。

Apply() vs Parallel_apply()的实验结果(图片由作者提供)
- 蓝色线图描述了
apply()方法的运行时间,黄色线图代表了parallel_apply()方法的运行时间。 - 我们将行数从 100 万改变到 1000 万,并注意到两种方法的运行时间与行数成正相关。
- 然而,
parallel_apply()方法在运行时间上比传统的apply()方法有了显著的改进。 - 随着行数的增加,两种方法的运行时间之间的差异也会增加。这表明您应该始终使用
parallel_apply()将函数应用于数据帧,尤其是在较大数据集的情况下。
结论
总之,在这篇文章中,我们在一组虚拟数据帧上比较了熊猫的apply()和 Pandarallel 的parallel_apply()方法的性能。
实验结果表明,就运行时间而言,使用parallel_apply()方法比apply()方法更有效——提供高达 4 到 5 倍的性能提升。
你可以在这里找到这篇文章的代码。
感谢阅读!
🚀订阅数据科学每日剂量。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。
🧑💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。
✉️ 注册我的电子邮件列表 不要错过另一篇关于数据科学指南、技巧和提示、机器学习、SQL、Python 等的文章。Medium 会将我的下一篇文章直接发送到你的收件箱。
我喜欢探索、试验和撰写关于数据科学概念和工具的文章。你可以在 LinkedIn 上和我联系。
PivotUI 简介:不要再使用 Pandas 来分组和透视数据
为每个人简化数据分析

动机
透视和分组操作是每个典型的表格数据分析过程的基础。pivot_table()和groupby()方法是熊猫最常用的方法之一。
分组主要用于理解分类数据,使您可以计算数据中单个组的统计数据。

分组的表示(作者的图像)
另一方面,数据透视表允许您将数据交叉制表,以便进行精细分析。

数据透视表的表示(图片由作者提供)
首先,我很欣赏数据透视表的重要性。然而,很多时候,我发现熊猫的旋转(特别是)有点吓人。我相信你们中的许多人会对此产生共鸣。
对于来自 Excel 的人来说,它提供了一个光滑而直观的 UI 来生成数据透视表,过渡到 Pandas 并不像人们预期的那样顺利。
此外,在许多数据分析任务中,事情通常不会停留在数据的旋转或分组上。许多人经常对创建图表感兴趣,以使它们更容易理解,这增加了编写更多代码的工作量。
如果我们有一个初学者友好的优雅的用户界面来对熊猫数据帧执行这些操作,就像我们在 Excel 中做的那样,那不是很好吗?
PivotTableJS 简介🚀!
PivotTableJS
顾名思义,PivotTableJS 是一个用于创建数据透视表(以及分组表)的 Javascript 库。
它的终极卖点是可以用在 Jupyter 笔记本上(阅读更多),不用写任何代码。因此,您可以编写代码(在任何需要的地方),修改数据,并立即将其提交给 PivotTableJS。
此外,其拖放功能和直观的布局使执行聚合、创建数据透视表和绘制交互式图表的任务变得轻松快捷。
安装 PivotTableJS
要使用pip安装 PivotTableJS,请在终端中键入以下命令。
或者,您也可以conda:
入门指南
加载数据集
当然,第一步是使用 Pandas 加载数据集。
出于演示目的,我将使用一个包含虚假员工信息的虚拟数据集,它是我使用 Faker 创建的。
调用 PivotUI
将数据集作为 Pandas DataFrame ( df)加载后,从pivottablejs库中导入pivot_ui方法。
在此之后,Jupyter 的输出面板中将出现以下窗口。
这些列显示在界面最左侧的面板中。默认显示计算区域的记录数(此处1,000)。

pivot_ui()方法的输出(图片来自作者)
您可以将列拖到两个空框中,以执行groupby和pivot操作,并从aggregations下拉列表中选择适当的聚合。

pivot_ui()方法的输出(图片来自作者)
最后,除了 aggregations 下拉列表,您还可以看到另一个下拉列表(当前显示的是Table)。这用于选择输出格式:

更改输出格式(Gif by Author)
接下来,让我们了解如何使用该工具执行groupby和pivot。
使用 PivotTableJS 分组
若要对单个(或多个)列执行分组,请将它们拖到聚合下面的空白区域。
例如,假设我们想要在Employee_Status列上分组。下面演示了这一点:

PivotTableJS 中的分组(Gif by Author)
就这么简单。
这里默认的聚合是组的大小(Count)。您可以更改这一点,并使用所需的聚合方法对您选择的任何列执行该操作。
假设我们想要找到Employee_Status列中每个值的平均值Employee_Rating。下面演示了这一点:

更改 PivotTableJS 中分组的聚合(Gif by Author)
事情不止于此。您也可以更改输出格式。
在上面的演示中,我们希望将分组在Employee_Status列上的平均值Employee_Rating显示为条形图。您可以这样做:

在 PivotTableJS 中绘制分组结果(Gif by Author)
很酷,不是吗?
想象一下,用代码做同样的事情要花多少时间。这是快速和不费力的。
使用 PivotTableJS 的数据透视表
与 GroupBy 类似,使用 PivotTableJS 生成数据透视表也相当简单。
只需要一个额外的步骤。在 groupby 示例中,我们只将列拖到一个空面板上。
然而,由于数据透视表的行和列都来源于表中的值,我们也应该拖动一个标题行。
例如,假设您想要显示包含Employee_Status和Employee_City列的数据透视表。这可以通过以下方式完成:

在 PivotTableJS 中创建数据透视表(Gif by Author)
您可能已经注意到了,这一次,我们还将一列拖到了上面的面板,这创建了一个数据透视表,而不是一个分组。
其余的事情与上一节中讨论的一样。
您可以从aggregation下拉列表中更改聚合,并选择另一列。
此外,为了更好地理解,您还可以将数据表示为图表。
结论
说到这里,我们就到此为止了。我希望你学到了新东西。
我相信,这个不可思议的工具将在执行一些典型的数据分析任务时为您节省大量时间。
觉得这个提示有意思?
如果你想了解更多关于数据科学和 Python 的优雅技巧和诀窍,我每天都会在 LinkedIn 上发布一个信息丰富的提示。
你可以在 我的 LinkedIn 帖子存档 中找到我发布的所有提示。可以在 LinkedIn 上关注我,看看以后所有的帖子。
或者,您也可以通过电子邮件订阅以下内容:
🚀订阅数据科学每日一剂。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。
🧑💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。
感谢阅读!
PyScript 简介—如何在浏览器中运行 Python
原文:https://towardsdatascience.com/introducing-pyscript-how-to-run-python-in-your-browser-8d838bb12224
Python 是现在新的 JavaScript 吗?嗯,不,但你仍然可以做很酷的事情

文章缩略图(图片由作者提供)
Anaconda 的首席执行官王蒙杰在 PyCon US 2022 期间透露了一些非常有趣的事情。它是py script——一个 JavaScript 框架,允许你在浏览器中创建 Python 应用。没错,现在您可以直接在 HTML 文件中嵌入 Python 代码,就像任何 JavaScript 开发人员都会做的那样。
PyScript 背后的想法很简单——数据科学家可以用他们最喜欢的语言围绕他们的模型创建网站,前提是他们具备基本的 HTML 知识。HTML 在某个方面有点像 SQL 每个人都知道。
【PyScript 会不负众望吗?只有时间会证明一切。今天,我将向您展示两个基本示例,让您在几分钟内开始使用 PyScript。如果您希望涵盖更高级的主题,比如机器学习和处理用户输入,请告诉我。
不想看书?请观看我的视频:
什么是 PyScript,为什么要关注它?
据 Anaconda Cloud 称,PyScript 是一个在 HTML(类似 PHP)中交错 Python 的系统。这意味着您可以用 HTML 编写和运行 Python 代码,调用 JavaScript 库,并处理几乎所有与 web 开发相关的事情。
对于数据科学家来说,这意味着您现在可以围绕您的数据产品创建 web 应用程序。每个人都可以使用网络浏览器,这使得网络成为一个比手机更容易进入的平台。
此外,您不必担心部署,因为一切都发生在浏览器中。您可以将您的模型和仪表板作为 HTML 文件共享,只要有人在 web 浏览器中打开它,它就会运行代码。整洁!
PyScript 构建在 Pyodide 之上,是 CPython 到 WebAssembly/Emscripten 的一个端口:

图 1 — PyScript 技术堆栈(来源:https://anaconda.cloud/pyscript-python-in-the-browser))
如果你想知道,WebAssembly 是一项使用 Python 编写网站成为可能的技术。据 Mozilla 称,这是一种低级的类似汇编的语言,具有紧凑的二进制格式,以接近本机的性能运行,并为语言提供编译目标,以便它们可以在网络上运行。
但是实际上如何使用 PyScript 呢?接下来我们来回答这个问题。
如何使用 PyScript
你可以在pyscript.net上下载 PyScript 的 alpha 版本。我们今天不会下载任何东西。相反,我们将在 HTML 文件中嵌入一个样式表和一个脚本。作为参考,所有使用 PyScript 的 HTML 文件都必须有一个指向以下内容的链接:
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
PyScript 允许您使用以下组件用 HTML 编写 Python:
py-env-定义运行 Python 代码所需的 Python 包。py-script-编写 Python 代码的 HTML 文件的一部分。py-repl-创建 REPL 组件,评估用户输入的代码并显示结果。
我们今天将处理前两个问题,把最后一个问题留待以后处理。
让我们看看你如何用 PyScript 编写一个简单的 Hello World 程序。
PyScript 示例# 1-Hello World
在 Visual Studio 代码中,您可以编写一个感叹号,后跟一个制表符,以创建一个基本的 HTML 文档结构。在<head>标签内,添加上一节提到的两个链接。
hello world 示例不需要任何外部 Python 库,所以我们可以完全放弃<py-env>部分。
在关闭</body>标签之前,打开一个包含 Python 逻辑的新的<py-script>标签。只需注意压痕 -最好将其完全去除。格式看起来不太好,但是你会得到一个错误。
我们要做的很简单——打印一条 hello world 消息和当前的日期时间信息:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<title>First Python HTML app</title>
</head>
<body>
<py-script>
from datetime import datetime print("Hello World!")
print(f"It's now {datetime.now()}")
</py-script>
</body>
</html>
在浏览器中打开 HTML 文件,几秒钟后您将看到:

图像 2-在浏览器中使用 Python-Hello World 示例(作者提供的图像)
就在那里!这将花费您更多的时间在 Flask 中编码,所以 PyScript 已经显示出它是一个可行的替代方案。接下来让我们看一个更复杂的例子。
PyScript 示例# 2-带 Bokeh 的图表
没有图表,您无法围绕您的数据产品创建仪表板。到目前为止,PyScript 不支持 Plotly,所以我们只能选择 Matplotlib 或 Bokeh。让我们选择后者。
它需要大量的 Bokeh JavaScript 脚本,所以只需从下面的代码片段中复制它们。
这一次,我们依赖于一个外部 Python 库——Bokeh——所以一定要在<py-env>标签中指定它。我从博凯画廊复制了图表示例,只是为了确保它有效:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js"></script>
<title>Bokeh Example</title>
<py-env>
- bokeh
</py-env>
</head>
<body>
<h1>Bokeh Example in PyScript</h1>
<div id="chart"></div> <py-script>
import json
import pyodide
from js import Bokeh, console, JSON
from bokeh.embed import json_item
from bokeh.plotting import figure
from bokeh.resources import CDNfruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
counts = [5, 3, 4, 2, 4, 6]
p = figure(x_range=fruits, height=350, title="Fruit Counts", toolbar_location=None, tools="")
p.vbar(x=fruits, top=counts, width=0.9)
p.xgrid.grid_line_color = None
p.y_range.start = 0
p_json = json.dumps(json_item(p, "chart"))
Bokeh.embed.embed_item(JSON.parse(p_json))
</py-script>
</body>
</html>

图像 3-在浏览器中使用 Python-Bokeh 示例(作者图像)
你看看那个!不需要为了显示一个简单的图表而编写整个 web 应用程序。这就是 PyScript 的强大之处。
Python for Web 与 py script——何去何从?
长话短说,PyScript 简化了可视化和交互式展示数据产品的过程。这是最好的解决方案吗?没有,至少现在还没有。是不是最容易上手的?遥遥领先。
我很高兴看到未来的 PyScript 版本会带来什么。如前所述,它仍处于 alpha 阶段,因此预计未来会有很多变化。如果您对 PyScript 的更高级用法感兴趣,请告诉我—例如,围绕机器学习模型创建仪表板。
你对 PyScript 有什么想法?你计划用它来代替基本的 Python web 应用程序吗?请在下面的评论区告诉我。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
推荐阅读
保持联系
- 雇用我作为一名技术作家
- 订阅 YouTube
- 在 LinkedIn 上连接
原载于 2022 年 5 月 4 日https://betterdatascience.com。
重载介绍:永远不要再次运行 Python 代码来打印更多的细节
在运行时修改代码,节省工作时间

动机
在运行 Python 脚本时,我经常发现自己忘记打印所有必要的细节来跟踪管道的进度。
这通常在训练机器学习模型中观察到。大多数情况下,人们(包括我)经常忘记:
- 添加必要的日志详细信息。
- 打印基本的培训细节/指标,如准确度、误差、精确度等。
- 在每个
k时期后保存模型,等等。
我相信你也去过那里。
当然,问题不仅仅局限于机器学习。许多人在其他领域也面临着同样的问题,比如 Web 报废,人们在运行他们的代码后意识到他们应该丢弃更多的细节,等等。
别无选择,人们必须不情愿地停止代码,添加必要的细节并再次重新运行代码。如果您的管道已经运行了几个小时,这可能会令人非常沮丧。
但是如果我告诉你有一个巧妙的技巧呢?换句话说,在不丢失当前进度的情况下,对已经运行的代码进行更改实际上是可能的。
这就是我将在这篇博客中探讨的内容。
我们开始吧🚀!
再装
重载顾名思义,是一个 Python 库,允许你在每次迭代之前从源码中重载一个循环(或者函数)。
因此,您可以修改已经运行的代码并向其添加更多细节,而不会丢失任何当前进度。是不是很酷?
要安装它,请使用以下命令:
pip install reloading
重新加载循环
假设您有一个循环,它取一个初始值,并在每次迭代后减半。
但是,我们错误地忘记了在这个循环中打印迭代号,现在想修改它。
当然,没有重装,你别无选择,只能重新运行。
但是,如果您想在每次迭代之前重新加载for循环的主体,请用reloading包装迭代器,如下所示:
现在,您可以在运行时修改代码。下面显示了一个演示:

重装演示(作者 Gif)
如上所述,我们修改了for循环的主体。因此,我们可以在输出面板中看到新的细节,同时保持当前的进度。
重新加载函数
与重新加载循环类似,您也可以在每次迭代后重新加载函数体。考虑下面的函数half_value:
要重新加载函数体,用reloading装饰器装饰它。如下所示:
现在,您可以在运行时修改函数。下面显示了一个演示:

重装演示(作者 Gif)
完美!
结论
说到这里,我们就到此为止了。我希望你学到了新东西。
我相信这个技巧会在运行一些高运行时间代码时为你节省大量时间,比如训练机器学习模型。
觉得这个提示有趣吗?
如果你想了解更多关于数据科学和 Python 的优雅技巧和诀窍,我每天都会在 LinkedIn 上发布一个信息丰富的提示。
你可以在 我的 LinkedIn 帖子存档 中找到我发布的所有提示。你可以在 LinkedIn 上关注我,看看以后所有的帖子。
或者,您也可以通过电子邮件接收:
🚀订阅数据科学每日一剂。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。
🧑💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。
获取机器学习领域排名前 1%的研究论文、新闻、报道、推文的每周汇总。
我喜欢探索、实验和撰写关于数据科学概念和工具的文章。你可以在 LinkedIn 上和我联系。
感谢阅读!
Jupyter 笔记本电脑快照测试简介
原文:https://towardsdatascience.com/introducing-snapshot-testing-for-jupyter-notebooks-896512971df8
数据科学软件工程
nbsnapshot 是一个开源软件包,它可以对笔记本的输出进行基准测试,以自动检测问题

图片作者。
动机
当在 Jupyter 笔记本上分析数据时,我会无意识地记住“经验法则”来确定我的结果是否正确。例如,我可能会打印一些汇总统计数据,如果这些数据与我过去看到的相差太多,我就会对这些数据产生怀疑。对于更复杂的分析,我经常创建诊断图(例如直方图),并在新数据到达时检查它们。
我做这种半手工测试已经很多年了,在与其他数据科学家交谈时,我意识到这很常见;然而,没有工具可以简化这个过程。所以我开始问自己,我们是否可以自动化这个测试过程。在与一名前端开发人员交谈时,我清楚地知道了解决方案,他向我介绍了快照测试的想法。
想象一个前端开发人员使用 HTML、CSS 和 Javascript 创作 UI。开发人员如何测试用户界面是否正确?你可以说一些用户界面比其他的更好(如果它们更容易使用的话),但是正确性的论点在这里并不适用。所以我们最好的办法是使用我们以前的实现作为基准。例如,如果按钮的位置发生了意外变化,我们可能会引发错误。
我们可以将同样的想法应用于笔记本测试。假设您有一个笔记本,它清理输入数据集,计算一些指标(例如,给定列上 NAs 的百分比),然后存储清理后的数据集以供进一步分析:
假设我们今天运行这个笔记本。然后,一个星期过去了,我们运行笔记本并获得相同的结果。然后,两个星期过去了,我们运行笔记本,并意识到我们的指标看起来与前两个值非常不同。

第三周的结果与前两周相差太多。
我们不知道原因是什么,但我们知道发生了一些事情。因此,我们继续分析这种情况:可能是输入数据发生了变化,也可能是我们的代码包含了一些错误。不管那是什么,很高兴我们发现了这个,这样我们就可以进一步调查了。
快照测试旨在促进这一过程:我们将自动对笔记本电脑的输出进行基准测试,以检测潜在的问题。
介绍nbsnapshot
据我们所知, nbsnapshot 是 Jupyter 笔记本的第一个快照测试实现,我们很高兴发布它!
通过主演 GitHub 库来表示你的支持!谢谢!
想法很简单,每次我们运行我们的笔记本,我们记录我们想要基准的输出。然后,一旦我们有了至少两个历史记录,我们就开始将新值与历史值进行比较。最后,如果新值偏离(默认情况下,它使用三个标准差作为阈值)记录的值,我们会显示一个错误。让我们来看一个完整的例子。
首先,让我们导入几个包,并声明我们将使用的两个函数:
下载笔记本样本:
让我们创建一个假的历史文件来模拟我们运行一个笔记本(只有一个单元格)10 次。为了生成一个假输出,我们从以10(和1的标准偏差)为中心的正态分布中提取值:
控制台输出:(1/1):

让我们运行示例笔记本(constant.ipynb)。这个笔记本只有一个打印数字10的单元格。因为这个数字在我们的假历史的界限内,所以测试通过:
控制台输出:(1/1):
Testing: 'metric' - OK!
现在,让我们改写现有的历史,并用从以0为中心的正态分布中抽取的数字来替换它:
控制台输出:(1/1):

重新运行笔记本。这一次,笔记本里的数值(10)偏离历史记录太多。因此,测试失败:
控制台输出:(1/1):
Testing 'metric' - FAIL! Value is too high (10), expected one between -4.17 and 3.92
就是这样!这种简单的方法让我们无需额外的努力就能检测出笔记本中的问题!
用例
这里有两个重要的用例,其中快照测试非常有用。
监测数据质量
假设你有一个笔记本,每周都要接收数据。您可以使用快照测试来衡量所接收数据的质量,如果质量突然下降,就抛出一个错误。例如,您可能有一个在名为age的列中打印 NAs 百分比的单元格:

nbsnapshot 将在星期五抛出一个错误,因为该值是意外的。
在上表中可以看到,从周一到周四,NAs 的百分比与1%有一点偏离,但是到了周五,却暴涨到了5%,一定是发生了什么事情。
另一个示例测试是计算age列中具有负值的观察值的数量。该测试将允许您检测诸如-2或-10之类的损坏值何时进入笔记本。
监控模特训练表现
假设您有一个笔记本,它使用一个训练数据集作为输入,并输出一个训练好的模型。如果模型性能指标(例如准确性)突然下降,您可以使用快照测试来抛出一个错误。快照测试可以帮助您检测诸如损坏的数据、模型错误配置或数据泄漏等问题。
例如,假设您一直在训练模型,突然,模型精度下降了:

由于性能显著下降,nbsnapshot 将在第五次实验时抛出错误。
了解这种突然的变化将允许你进一步调查,找出发生了什么。
使用快照测试进行持续集成
为了有效地测试,我们建议您以持续集成的方式实现快照测试;这意味着无论何时修改源代码都要运行测试。例如,您可以使用 GitHub Actions 并在每次提交项目时运行以下命令:
每当您将更改推送到您的存储库时,这个命令将运行测试。
考虑
nbsnapshot支持查看同一笔记本中的多个单元格;然而,考虑到您进行的测试越多,由于假阳性而导致任何检查失败的可能性就越大。所以我们建议只测试几个基本的指标。在输出具有确定性的情况下,您可以根据需要检查任意多个像元。
快照测试需要手动更新历史结果。例如,假设您一直在训练一个模型,并且在最近十次实验中,历史精度一直在 0.80 左右;然后,你意识到那些实验有数据泄漏。修复问题后,精度下降到 0.70。因此,如果您运行快照测试,它将会失败。但是,在这种情况下,错误出现在历史记录中,因此您应该删除它们并用新值 0.70 替换它们。进行这种清理需要额外的努力,但是从长远来看,快照测试节省的时间是值得的。
结束语
在这篇博文中,我们介绍了 Jupyter 笔记本的快照测试,这个简单的想法可以极大地改善数据科学家的工作流程。请给 nbsnapshot 一个尝试。该库是新的,所以如果您有任何功能需求,请告诉我们!nbsnapshot 的完美补充是 Ploomber ,我们的开源框架用于编排基于笔记本的管道。
嗨!我叫爱德华多!我喜欢写所有关于数据科学的东西,并为开源项目做贡献。如果您想了解我的最新内容。跟我上 中 或 碎碎念 。感谢阅读!
介绍 Rich CLI 工具:在终端中查看文件将不再一样
现在你可以超越用 iTerm2 和 oh-my-zsh 来推销你的终端了。Rich CLI 将允许您在命令行上查看文件的方式上添加一个全新的维度。通过提供一个添加颜色和格式的工具包,Rich CLI 不仅将 CSV 文件转换成漂亮的表格,使您的编码脚本看起来就像在您选择的 IDE 中一样,并为 Markdown 和 JSON 文件提供特殊的格式,而且它还允许您自己格式化您在命令行上输入的文本。

Rich_CLI
Rich CLI 由 Will McGugan 创建,可在 Mac OS、Linux 和 Windows 上使用。Will 也是名为 Rich 的 Python 库和名为 Textual 的 Python 框架的创建者。丰富的 Python 库使 Python 程序员能够向终端呈现丰富的文本输出,并且已经被各种主流 Python 项目所采用。Rich CLI 的发布为命令行带来了丰富的 Python 库功能。
"你再也不会使用内置的猫,头或尾命令了!"
丰富的 CLI —安装
只需使用pip install命令,就可以将丰富的 CLI 工具安装到特定的目录或虚拟环境中…
python -m pip install rich-cli
但是,如果您希望能够在您的计算机上全局使用 Rich CLI(我相信一旦您开始使用它,您将希望能够在任何地方使用它!)那么我会推荐用 pipx 安装。
随 pipx 安装的工具和软件包存储在一个全局虚拟环境中,您可以通过命令行、所有全局目录和所有虚拟环境访问该环境。
要在 Mac 上安装 pipx,请在您的终端中运行以下命令。
brew install pipx
pipx ensurepath
然后使用 pipX 安装、升级或卸载软件包和工具,您只需使用pipx install、pipx upgrade和pipx uninstall,就像使用 pip 管理您的库一样。要安装 Rich CLI,只需使用以下命令。
pipx install rich-cli
丰富的 CLI —功能
Rich CLI 使用起来非常简单,开箱即可对各种文件类型进行漂亮的格式化(无需应用任何选项)。简单地使用Rich命令,后跟你选择的文件的路径,你会惊讶于你在终端上看到的输出。
rich Ex11_Prime_No_Function.py`

不应用选项的基本丰富输出
工具包提供了许多选项,您可以根据自己的喜好定制输出。我将简单地突出下面几个我最喜欢的,这样你就能感受到它的能力。要获得所有可用选项和参数的列表,只需在终端中键入rich --help即可调用内置的帮助功能。
行号和参考线
在终端中查看代码文件的一个重要特性是能够添加行号和缩进指南。这可以通过添加选项-n的行号和-g的引导线轻松实现。
rich Ex11_Prime_No_Function.py -n -g

带行号和参考线的 Python 脚本
主题
Rich CLI 甚至允许您设置一些将应用于显示文件的常见主题。可以为单个文件设置主题
rich Ex11_Prime_No_Function.py --theme monokai
或者在环境变量中设置为默认设置
RICH_THEME=monokai
rich Ex11_Prime_No_Function.py

带行号和指南的 Monokai 主题
在Pygments.org网站上的风格版块有大量主题可供选择。
寻呼机
如果文件中的行数太多,无法在标准终端窗口中轻松查看,那么您可以使用--pager选项在内置的寻呼机应用程序中打开文件,在该应用程序中,可以使用光标键或滚动条浏览文件。

说明滚动条的寻呼机应用程序
CSV 文件作为表格
丰富的 CLI 将显示 CSV 或 TSV 文件的内容在一个非常清晰,易于阅读的表格。当与上面说明的分页选项--pager结合使用时,您可以轻松地上下滚动来查看包含的数据集。

CSV 文件的列表显示
上面的例子几乎没有触及丰富的 CLI 功能的表面。其他一些值得一提的关键特性是:
-— head xx或—-tail xx允许您指定从文件开头或结尾开始的行数,以应用所选格式显示。- 特定的 JSON 和 MARKDOWN 格式
- 特定文本块宽度、对齐方式和对齐方式
- 由用户定义的符号构建的彩色标尺
- 丰富的印刷、面板和衬垫
- 从以 HTTP/HTTPS 开头的 URL 直接从互联网上读取文件
- 将格式化的输出(所有命令/选项)写入 HTML 文件
有关提供的所有命令/选项的完整详细信息,以及代码示例和示例输出图像,请参考 Rich CLI GitHub repo 中构造良好的 README.md 文件。
我真的鼓励你尝试 Rich CLI,如果你是 Python 开发者,那么你也应该看看 Rich 和 Textual。这三个项目仍在积极维护,并定期添加新功能。
我当然迫不及待地想看到在不久的将来,这些伟大的工具包中会添加哪些令人惊叹的新功能。
参考
如果您希望进一步研究我在本文中提到的工具,那么您可以通过下面的链接来研究它们的全部特性集…..
- 【https://github.com/Textualize/rich-cli
- https://github.com/Textualize/rich
- https://github.com/Textualize/textual
- https://github.com/pipxproject/pipx
怀特异方差一致性估计量介绍
HC 估计量的介绍,以及它在面对异方差时建立回归模型的重要性
在本文中,我们将讨论统计建模中的两个基本主题,即协方差矩阵和异方差。
协方差矩阵是统计推断的工作马。它们用于确定回归系数是否具有统计显著性(即不同于零),并用于构建每个系数的置信区间。为了完成这项工作,他们做了一些重要的假设。这些假设中的主要假设是模型的误差是同方差的,即它们具有恒定的方差,并且误差不是自相关的。
在实践中,有可能假设误差的非自相关性,特别是在横截面数据中(但不是在时间序列设置中)。
不幸的是,误差的同质性假设通常是不成立的。幸运的是,面对非恒定方差,即异方差回归误差,有办法建立回归系数的协方差矩阵(从而进行可靠的统计推断)。
在本文中,我们将研究一种被称为怀特异方差一致性估计器(以其创造者哈尔伯特怀特命名)的技术,其中我们将构建一个对异方差回归误差稳健的回归系数协方差矩阵。
本文是以下两部分系列的第 1 部分:
第 1 部分:介绍 White 的异方差一致性估计量
第 2 部分:使用 Python 和 Statsmodels 的 White 异方差一致性估计量教程
在第 1 部分中,我们将探讨 HC 估计量的理论,而在第 2 部分中,我们将通过一个基于 Python 的教程来介绍如何使用它来进行对异方差稳健的统计推断。
考虑回归模型的以下一般形式:

y 作为回归变量的函数 X ,系数和误差(图片由作者提供)**
在这个模型中,我们将响应变量 y 表示为回归变量矩阵 X 、系数 β 和回归误差 ϵ 的某个未指定函数。如果这个模型有 k 个回归系数包括截距,那么对于一个大小为 n , y 的数据集是一个大小为【n×1】, X 的矩阵【n×k】, β 是
该模型的线性形式由两个回归变量和一个截距组成,可表示如下:

包含 2 个回归变量和截距的线性模型(图片由作者提供)
这里, 1 ,x_ 2,x_ 3是大小为【n×1】的列向量,其中 1 只是一个 1s 的列向量。对于数据集中任何给定的行 i ,上述模型可以表示如下:**

数据集中第 I 行的线性模型(图片由作者提供)
实际上,将等式(1)或(1a)简洁地表达如下是有用的:

线性模型(图片由作者提供)
或者在其全矩阵中荣耀如下(在我们的例子中,k=3):

矩阵形式的线性模型(图片由作者提供)
β_1,β_2,…β_k 代表样本为整个总体时对应的系数的真实总体水平值。
但是对于所有实际情况,样本数据集是来自总体的某个大小为 n 的随机子集。当线性模型被训练为“拟合”该样本数据集时,我们得到拟合模型的以下等式:

拟合线性模型的方程(图片由作者提供)
在这里, y 和 X 的含义和以前一样。但是β_ cap是系数 β_1_cap,β_2_cap,…β_k_cap 的拟合值的向量, e 是拟合模型的残差的向量。注意回归模型的误差项**(y—xβ))和残差项e=(y)**
由于在不同的样本上拟合模型,每个大小为 n,的样本每次将产生一组不同的系数值,拟合的系数 β_1_cap,β_2_cap,…β_k_cap 可被视为随机变量。拟合值 β_1_cap,β_2_cap,…β_k_cap 各有一个平均值,可以表示为相应的真实总体值 β_1,β_2,…β_k ,并且它们在该平均值附近有一个方差。
回归模型拟合系数的协方差矩阵包含拟合系数的方差,以及拟合系数之间的协方差。下面是包含 k 个回归变量(包括截距)的模型的矩阵图:

拟合回归系数的协方差矩阵(图片由作者提供)
协方差矩阵是大小为【k x k】的方阵。该矩阵中位置 (i,j) 处的元素包含第个和第个个拟合系数的协方差。沿着主对角线的值是 k 拟合系数的方差,而非对角线元素包含拟合系数之间的协方差。主对角线元素的平方根是拟合系数的标准误差。矩阵围绕主对角线对称。**
回归系数的协方差矩阵用于确定模型的系数是否具有统计显著性,并计算每个系数的置信区间。
对于形式为y=xβ+【ϵ(其形成统计建模的主干)的线性模型,协方差矩阵的公式在其内包含另一个协方差矩阵,即模型的误差项【ϵ的协方差矩阵。误差的协方差矩阵也是一个方阵。对于大小为 n 的数据集,该矩阵的大小为【n×n】,其结构如下:**

回归模型误差的协方差矩阵(图片由作者提供)
顺便提一下,注意这两个矩阵都包含条件方差和协方差,它们以回归矩阵 X 为条件。
正如拟合系数一样,每个误差ϵ_i都是具有均值和方差的随机变量。如果模型的误差是同方差的(常数方差),那么对于数据集中的每一行 i , Var(ϵ_i) 是某个常数σ。另外,如果误差不是自相关的,那么对于每对误差 ϵ_i 和 ϵ_j 其中 i!= j , Cov(ϵ_i,ϵ_j)=0 。因此,如果模型的误差是同伦的和不相关的,上述矩阵简化为相当简单的形式如下:****

当误差为同方差且非自相关时,回归模型误差的协方差矩阵(图片由作者提供)
其中 I 是大小为【n×n】的单位矩阵。单位矩阵是标量数 1 的等价矩阵。 I 的主对角线元素全为 1,其余元素为零。而正如数字 1 一样,(I)^n =I与 I 的逆也是 I 。**
在线性模型中,当模型的误差为同方差且非自相关时,拟合回归系数的协方差矩阵具有以下简单明了的形式,其推导在我关于协方差矩阵的文章中有所介绍:

模型误差为同伦齐次和非自相关时拟合回归系数的协方差矩阵公式(图片由作者提供)**
上式中,T5【XT7'是**X的转置。转置类似于通过交换行和列在侧面旋转 X 。由于有维度【n X k】,有维度【k X n】。X'X的乘积是两个大小分别为【k X n】【n X k】的矩阵的乘积,因此它是一个大小为【k X k】的方阵。上标为 -1 的倒数也是【k x k】的大小。最后,我们用系数 σ 对倒数进行缩放,该系数是所有误差项的恒定方差。******
等式(6)表明,为了计算拟合系数的协方差矩阵,必须访问 X 和 σ。虽然实验者完全可以接触到 wx,但是回归模型的误差项 ϵ 是一个本质上不可观测的变量,其方差 σ也是如此。实验者所能做的最好的事情就是计算拟合模型的残差 e ,并用它们作为 ϵ 的代理。幸运的是,可以证明残差的方差 s 形成了模型误差方差 σ 的无偏估计,并且在残差已知的情况下, s 很容易计算。因此,在实践中,我们使用以下公式来估计拟合系数的方差:**

当模型误差为同方差和非自相关时,拟合回归系数的估计协方差矩阵的公式(图片由作者提供)**
不幸的是,现实很少是简单的。虽然我们通常可以假设误差项的非自相关性,特别是在横截面数据集中,但误差的异方差性在横截面和时间序列数据集中是非常常见的。
当误差项异方差时,误差的协方差矩阵如下所示:

当模型误差为异方差和非自相关时,回归模型误差的协方差矩阵(图片由作者提供)**
在这个矩阵中,σ只是我们从矩阵中提取出来的一个普通比例因子,因此每个 ω_i=σ _i/σ。很容易看出,当误差为同方差时, σ _i=σ 对于所有 i 和 ω_i=1 对于所有 i 和ω=I,恒等式矩阵。
顺便提一下,一些统计文本使用ω来表示误差的协方差矩阵,该协方差矩阵是异方差和自相关的。**
无论哪种方式,当协方差误差的矩阵不是 σ I ,而是σω时,拟合回归系数的协方差矩阵失去了等式(6)或(6a)给出的简单形式,而是呈现如下所示的不祥形式:**

模型误差异方差时估计系数协方差矩阵的公式(图片由作者提供)
如果你好奇 Eq (8)是怎么推导出来的,我在文末已经提到了它的推导。
现在,我们需要了解面对异方差误差,如何使用等式(8)最佳地估计拟合系数的方差。
等式(8)由三段组成(分别用绿色、黄色和绿色着色)。由于 X 矩阵对于实验者来说是完全可访问的,所以术语和(x’x)^(-1)是容易计算的,因此可以计算绿色块。问题出在ω和方差因子σ , 两者都是不可观测的,因为它们涉及回归模型的误差项【ϵ。因此,等式中心的黄色部分无法计算。****
回想一下,当误差是同方差且非自相关时,我们可以使用残差的方差 s I 来估计σ I 。同样,在这种情况下我们需要一种方法来估计(σ【ω】这将使黄色位可计算。
这就是由 Halbert White 在 1980 年提出的估计量发挥作用的地方。怀特提出了一种估算等式(8)核心的黄色项(X‘σωX)的方法。具体来说,他证明了以下几点:**

哈尔伯特·怀特在他 1980 年的论文中证明的身份(图片由作者提供)
等式(9)值得解释一下。方程(9)左 h 上的 plim 算子代表概率极限。简而言之,当数据集 n 趋向于无穷大时,通过对等式(9) 的 L.H.S .求和计算的随机变量以概率 收敛于 R.H.S .上的随机变量。理解“依概率收敛”的一个简单方法是想象两个随机变量 A 和 B 。如果 A 在概率上收敛于 B ,则意味着随着 n (数据样本的大小)的增加, A 的概率分布变得越来越像概率分布 B ,并且随着 n 趋于无穷大,它变得(几乎)等同于 B 的概率分布。因此,当 n 变得任意大时, A 的属性变得与 B 的属性无法区分。**
等式(9)的 L.H.S .上的和以及 R.H.S .上的项是随机变量的事实可以推导如下:
让我们看看方程(9)的 R.H.S。每次随机选择大小为 n、 X 的样本时,回归矩阵很可能会呈现不同的一组值。这使得 X 矩阵成为具有某种(未知)概率分布的随机变量。等式(9)的 R.H.S .项,即,(X【σωX)/n,是的函数,这使得它成为具有某种概率分布的随机变量。**
现在我们来看看 L.H.S .上的 L.H.S .我们有术语X’_ I和 x _i 分别是第 I 行×第** 和第 I 行的转置。两者都是随机变量,原因与 X 和X’都是随机变量一样。因此,lhs 上的整个比例求和也是具有某种概率分布的随机变量。****
让我们检查 lhs 上的总和:

(图片由作者提供)
X_ I是 X 的第 I 行,所以它的一个大小为【1 X k】的行向量, x '_i,它的转置大小为【k X 1】。因此他们的矩阵乘积是一个【k X k】方阵,它包含了第 I 行 X 的协方差。我们用第 I 行的剩余误差的平方来缩放这个矩阵。求和对 n 个这样的【k x k】矩阵进行求和,每个矩阵由来自拟合模型的相应残差平方进行缩放。求和的结果是一个大小为【k x k】的方阵:**

将等式(9)的 L.H.S .计算为 n 个大小为[k x k]的矩阵的缩放求和(图片由作者提供)
现在让我们看看等式(9)的均方根值:

(图片由作者提供)
X 是大小为【n X k】的回归矩阵,使其转置大小为【k X n】的X’。 σ 是标量。ω尺寸【n x n】。从左到右,X 'ω的尺寸为【k X n】。而(X 'ω)X则是尺寸【k X k】。 因此,R.H.S .也是一个大小为 [k x k] 的平方矩阵,与标量 σ /n. 成比例**
有了这个估计,我们再来看看面对异方差误差时拟合系数的协方差公式:

模型误差异方差时估计系数协方差矩阵的公式(图片由作者提供)
多亏了 White 博士,我们现在有了一种方法来估计等式(8)中心的黄色项,如下所示:

怀特的异方差一致性估计量(图片由作者提供)
等式(10)被称为怀特的异方差一致性(HC)估计量。它为回归建模者提供了一种面对异方差误差时估计拟合回归系数的渐近协方差矩阵的方法。“渐近”一词意味着,严格地说,估计量只对无限大的数据集有效。更多关于这个事实在下面。**
在使用它时,我们应该记住它的以下两个限制:
回想一下,这个估计量是基于一个恒等式,这个恒等式只有在数据集变得任意大时才有效,也就是说,在技术上是 n → ∞。在实际设置中,这一限制使得该估计器仅对于非常大的数据集特别有效。对于较小的数据集(称为小样本),比如说当 n 小于或等于几百个数据点时,怀特的 HC 估计器往往会低估拟合系数的方差,使它们看起来比实际更相关。小样本中的这种低估通常可以通过将等式(10)除以(n-k)来纠正,如 MacKinnon 和 White 在他们 1985 年的论文中所示(参见文章末尾的论文链接)。**
White 的 HC 估计量的第二个潜在问题是,它假设回归模型的误差中几乎没有自相关。这一假设使其仅适用于横截面和面板数据集,并使其特别不适用于通常包含延伸到过去几个时期的自相关的时间序列数据集。
总之,White 的异方差一致性估计量提供了一种强有力的方法来估计拟合系数的协方差矩阵,从而在异方差面前进行一致的统计推断。
系数估计的协方差矩阵的推导
我们从线性模型开始:

线性模型的方程(图片由作者提供)
β 的最小二乘估计产生以下估计量:

的 OLS 估计器(图片由作者提供)
将(b)中的 y 替换为xβ+【ϵ】并重新排列术语得出如下推导结果:**

(图片由作者提供)
随机变量的方差是其均值减去后的值的平方的期望值:Var(X)= E[(X——X【均值】]。系数估计值的均值β_ cap简单来说就是总体值 β 。因此,我们继续进行如下推导:**

拟合回归模型的系数估计方差的推导(图片由作者提供)
等式(e)中间的蓝色项是回归模型误差的协方差,取决于 X 。我们知道误差的协方差矩阵可以表示为σω。因此,当模型误差异方差时,用∑ω替换等式(c)中的蓝色期望值,得到估计系数的协方差矩阵的公式:****

模型误差异方差时估计系数协方差矩阵的公式(图片由作者提供)
在我下周的文章中,我们将通过 Python 和 Statsmodels 来浏览如何使用 White 的异方差一致性估计的教程。敬请期待!
参考文献、引文和版权
报纸
怀特,哈尔波特。"异方差一致性协方差矩阵估计和异方差的直接检验."计量经济学,第 48 卷,第 4 期,1980 年,第 817–38 页。JSTOR ,https://doi.org/10.2307/1912934.2022 年 9 月 25 日访问。PDF 下载链接 **
James G MacKinnon,Halbert White,具有改进的有限样本性质的一些异方差一致性协方差矩阵估计量,计量经济学杂志,第 29 卷,第 3 期,1985 年,第 305-325 页,ISSN 0304-4076,https://doi . org/10.1016/0304-4076(85)90158-7。(https://www . science direct . com/science/article/pii/0304407685901587)PDF 下载链接**
形象
本文中所有图片的版权归 CC-BY-NC-SA 所有,除非图片下面提到了不同的来源和版权。
如果你喜欢这篇文章,请关注我的Sachin Date获取关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。
tidyverse 简介——为数据分析师解决 R
tidyverse 库包提供了一种简单的“类似 SQL 的方式”在 R

r 不需要很难。图片来自 Pixabay
我刚刚开始了计算机科学的大学学习,并渴望通过 R 学习数据分析的秘密。在计算了几个月的统计问题的答案后,令我失望的是,我意识到如果不查阅课程 R 手册,我仍然只能编写几行代码。所有的方括号、逗号和美元符号在我的脑海里混为一谈,甚至连过滤数据框中的行这样简单的任务都感觉远非简单明了。
我开始认为 R 和我并不兼容,并决定每当我需要分析数据时就求助于 python 和 pandas。
你也很难理解 R 吗?如果是这样,我有好消息告诉你。使用 R 有一种更简单、更直观的方法:tidyverse 库!它不仅仅是另一套工具。这是 r 中完全不同的数据分析方式。
我第一次真正接触 tidyverse 是在我开始为现在的雇主工作的时候。他们在 R 中完成了大部分高级分析。当我承认我的 R 技能很差时,我很惊讶地听到我的同事回答说没关系,因为他们反正用 tidyverse!
介绍 tidyverse
tidyverse 是一个 R 包集合,是专门为数据分析师和科学家的需求而开发的。该集合包括许多不同类型场景的逻辑,例如,时间智能、数据争论和字符串操作。
软件包中包含的功能设计得尽可能直观易用,并且只需要最少的调整。一个例子是管道,即。%>%-符号。管道的功能是将不同的数据争论步骤链接在一起,而不必将每个步骤的结果保存到不同的变量中。这使得代码更加整洁,编写速度更快。看看下面伪代码在逻辑上的区别。

base R 与 tidyverse 中数据争论步骤的链接
另一个例子是常见数据框修改函数的类似 SQL 的命名。要过滤数据框中的行,可以使用 filter 函数,要选择特定的列,可以使用 select 函数。
另一个很好的例子是扫视函数。Glimpse 的主要优点是它可以垂直打印列名。相比之下,头——检查数据的基本 R 函数——水平打印它们。使用glow,所有的列名将适合打印区域,不管有多少。

Base R 的头部功能 vs tidyverse 的一瞥功能
与 tidyverse 的数据争论
了解 tidyverse 好处的最好方法之一是尝试一些基本的数据角力练习;选择一些列,过滤一些行并操作一些值。让我们用内置的 虹膜数据集来试试这些东西。
在开始争论之前,下载并加载 tidyverse。这可能需要几分钟时间。
install.packages("tidyverse")
library(tidyverse)
创建新数据:三表
iris 数据集非常有限,只有关于花的种类和尺寸的信息。让我们通过链接到每个物种的维基百科页面来丰富数据。我们可以通过使用三重函数创建新的数据框来实现这一点。
列名前用 ~ 标记,每列内容用逗号分隔。
iris_wikipedia <- tribble(
~ Species,
~ Wikipedia,
'setosa',
'[https://en.wikipedia.org/wiki/Iris_setosa'](https://en.wikipedia.org/wiki/Iris_setosa'),
'versicolor',
'[https://en.wikipedia.org/wiki/Iris_versicolor'](https://en.wikipedia.org/wiki/Iris_versicolor'),
'virginica',
'[https://en.wikipedia.org/wiki/Iris_virginica'](https://en.wikipedia.org/wiki/Iris_virginica')
)
结合其他数据:加入
现在,当我们有了维基百科的数据,我们可以用一个连接把它和虹膜数据集结合起来。tidyverse 提供了几种类型的连接,但是我们选择左连接。物种列将作为连接中的一个键,我们必须确保两个数据集中的数据类型相同。在 iris 数据集中,数据类型是 factor,而在 iris_wikipedia 中是 character。
让我们用 mutate 函数改变后者的数据类型(下面将进一步介绍这个函数)。
iris_wikipedia <- tribble(
~ Species,
~ Wikipedia,
'setosa',
'[https://en.wikipedia.org/wiki/Iris_setosa'](https://en.wikipedia.org/wiki/Iris_setosa'),
'versicolor',
'[https://en.wikipedia.org/wiki/Iris_versicolor'](https://en.wikipedia.org/wiki/Iris_versicolor'),
'virginica',
'[https://en.wikipedia.org/wiki/Iris_virginica'](https://en.wikipedia.org/wiki/Iris_virginica')
) **%>%
mutate(Species = as.factor(Species))**
在定义了原始的虹膜数据集作为我们的起点之后,我们取它,并借助 tidyverse 的管道函数将其发送给 left_join 函数。如果数据集包含具有相同名称和数据类型的列,只需将新数据集的名称提供给 left_join 。如果列名不同,则通过参数by = [c](https://rdrr.io/r/base/c.html)("column_name_in_base_dataset" = "column_name_in_new_dataset")将键作为分配给的向量提供
our_wrangled_data <- iris %>%
left_join(iris_wikipedia)
过滤行:过滤
假设我们只想在分析中包括“圣淘沙”这个物种。我们通过将上一步的输出通过管道传输到过滤器函数来实现。注意,这里使用了两个等号。不等于被定义为!=.
our_wrangled_data <- iris %>%
left_join(iris_wikipedia) **%>%
filter(Species == "setosa")**
选择列:选择
我们只对 sepal 和维基百科相关的数据感兴趣,因此只选择那些栏目。select 函数既可以用来选择我们想要的列,也可以用来选择我们不想要的列。在后一种情况下,我们只需在列名前添加一个减号:select(-unwanted_column_name)。
our_wrangled_data <- iris %>%
left_join(iris_wikipedia) %>%
filter(Species == "setosa") **%>%
select(Sepal.Length, Sepal.Width, Wikipedia)**
创建新列或修改现有列: mutate
函数 mutate 既可以用于更改现有列的内容,也可以用于创建具有指定逻辑的新列。
在本例中,我们创建了新的列 Long。Sepal 如果行的 Sepal,则获得布尔值 true。长度比所有刚毛萼片长度的平均值长。如果我们将新列命名为萼片。长度,它会覆盖现有的萼片。长度列。这是我们在加入之前对 iris_wikipedia 的物种栏目所做的。
our_wrangled_data <- iris %>%
left_join(iris_wikipedia) %>%
filter(Species == "setosa") %>%
select(Sepal.Length, Sepal.Width, Wikipedia) **%>%
mutate(Long.Sepal = if_else(Sepal.Length > mean(Sepal.Length),
TRUE,
FALSE))**
按具体栏目分组: group_by & 总结
在 tidyverse 中,group_by 既可用于创建一个汇总数据帧(SQL 中的 group by),也可用于创建一个汇总列,同时保留原始数据帧(SQL 中的窗口函数)。在前一种情况下,group_by 与 summarise 一起使用。在后一种情况下,它通常与 mutate 和 ungroup 一起使用,后者会移除 group_by 环境,因此可能的后续函数会应用于整个数据框,而不仅仅是分组的数据。
两个版本都试试吧。我们从基本分组开始,以便概括平均信息萼片。长度为基于其长度的观察值。萼片状态。
our_wrangled_data <- iris %>%
left_join(iris_wikipedia) %>%
filter(Species == "setosa") %>%
select(Sepal.Length, Sepal.Width, Wikipedia) %>%
mutate(Long.Sepal = if_else(Sepal.Length > mean(Sepal.Length),
TRUE,
FALSE)) **%>%**
**group_by(Long.Sepal) %>%
summarise(Mean.Sepal.Length = mean(Sepal.Length))**
我们得到以下输出:

将我们的数据框分组后输出
为了获得相同的聚合平均值信息,但保留数据帧结构,在使用 group_by 函数后,我们使用 mutate 【T0,】创建一个新列。这创建了与 summarise 函数相同的方法,但是行数保持不变。
由于这是最后一步,所以不需要取消和的组合。然而,为了防止我们以后添加更多的转换步骤,现在就把它包含进来是个好主意,这样以后就不会忘记了。
our_wrangled_data <- iris %>%
left_join(iris_wikipedia) %>%
filter(Species == "setosa") %>%
select(Sepal.Length, Sepal.Width, Wikipedia) %>%
mutate(Long.Sepal = if_else(Sepal.Length > mean(Sepal.Length),
TRUE,
FALSE)) **%>%
group_by(Long.Sepal) %>%
mutate(Mean.Sepal.Length.Sepal.Group = mean(Sepal.Length)) %>%
ungroup()**
输出如下所示:

创建分组列后的输出
结论
在这些和相关函数的帮助下,我估计我完成了 70 %的数据争论。缺少的一个主要东西是数据导入。我主要使用的是雪花和 Azure 存储连接器,但是对于大多数广泛使用的数据存储解决方案来说,R 连接器是存在的。
Wordle 基准测试简介
原文:https://towardsdatascience.com/introducing-wordle-benchmark-423f4f6d38a3
一个用于评估人工智能/人工智能代理的 Python 框架
你喜欢沃尔多吗?我喜欢沃尔多。我想看看我是否能教我的电脑玩一个像样的游戏,但我找不到一个给我的解决方案评分的好方法。
我组装了一个 Python 包来玩 Wordle 游戏,并为 Wordle“代理人”跟踪游戏统计数据:为玩 Wordle 而编写的 Python 软件。

基本 Wordle 游戏演示
入门指南
让我们浏览一下安装 wordle-benchmark 的步骤。
来自皮普
最简单的选择是 pip 安装。
来自 git
如果您想安装一个尚未在 PyPi 中反映的版本,您可以使用 pip 直接从 repo 安装。
从本地克隆
如果你想贡献源代码,你需要在本地安装。
为了调用脚本,需求文件中反映了一些额外的依赖关系。
样本使用
想自己写个 Wordle 代理看看能玩的多好?让我们探索一下与 wordle-benchmark 包交互的基本逻辑。我将详细介绍这个脚本的步骤。
一个“代理人”玩一个“游戏”并提交“猜词”来猜测“目标词”。“字典”存储所有有效的“猜测词”或“目标词”。您可以使用“目标词”列表运行多个“游戏”,并使用“基准测试”获得汇总统计数据
代理只需要实现一个抽象签名“play”
它以一个游戏作为论据,这个游戏包括你需要的所有信息,比如你已经猜了多少次,你匹配了哪些黑/黄/绿字母,字典中哪些单词仍然是可能的,等等。
为了演示这个实现,让我们以一个基本代理为例,它总是在第一轮预测“crane ”,并在随后的每一轮中猜测仍然可能出现的最常见的单词。
现在,我们可以根据单词列表对其进行评估,以计算其表现如何的统计数据。
现在我们来评价一下!
这个代理只是一个简单的例子!我等不及要看你做的特工了!
结论
Wordle-benchmark 提供了一个简单的界面来试验 Wordle 的 AI/ML 解决方案。这是一个相对简单的游戏,是一个很好的学习练习。试试看,让我知道你想到了什么!
如果你发现任何错误,让我知道!请随意投稿。留言或评论,如果你想出了有趣的 Wordle 代理!
适应性学习简介
原文:https://towardsdatascience.com/introduction-to-adaptive-learning-9a0ee48fb8a7
使用机器学习和数据科学来个性化教育

Alexandre Van Thuan 在 Unsplash 上拍摄的照片
数据科学(DS)和机器学习(ML)经常被用来构建个性化产品。个性化在教育中的应用包含了适应性学习的领域。DS/ML 的商业化程度相对较低的应用吸引了该领域的顶级研究人员,包括汤姆·米切尔(是的,汤姆·米切尔)经营公司致力于解决这个问题。
在这篇文章中,我们将深入探讨什么是自适应学习,基本概念,自适应学习系统的设计,以及该领域常用的 DS/ML 技术。
什么是适应性学习?
适应性学习旨在为个别学习者提供个性化的课程教学。在一个师生比例不断下降的时代,人们对创建能够支持教师和学生的学习系统有着极大的兴趣。适应性学习旨在通过提供实时反馈和适应学生的学习细微差别来支持学生。就教师而言,适应性学习可以帮助识别有后退风险的学生,并提供关于学生如何学习概念的见解,以随着时间的推移改进课程。
智能辅导系统
在文献中,利用自适应学习的解决方案通常被称为智能辅导系统(ITS)。典型的智能交通系统如下图所示。

作者基于引用创建的图像
从标有数字和突出显示的部分,我们可以看到智能交通系统以三种不同的方式适应学生的需求。
设计回路适应性
它可以从与课程互动的一组学生中收集数据,并将其提供给教师,教师可以为下一组学生更好地设计课程。这被称为设计循环适应性,其中课程基于数据一次适应整个队列。
任务循环适应性
它可以收集单个学习者的表现,因为他们与课程内容互动。然后,基于“领域模型”(连接课程概念的信息模型)、“学习者模型”(跟踪学习者到目前为止已经学习了哪一组概念以及有多大信心的模型)和“教学模型”(有一定信心已经学习了某一组概念的学习者接下来应该教什么的模型),ITS 可以调整下一组课程指令,称为任务循环适应性。因此,task loop 可以为个人学习者提供个性化的课程内容。
步进循环适应性
它还可以适应学习者在一项任务中的个人行为。例如,正在进行任务的学生可以收到关于他们是否正确执行了任务中的中间步骤的实时反馈,询问下一步的提示,了解中间步骤的影响等。这被称为“步进循环”适应性。步骤循环用于在任务中适应单个学习者的动作。
适应哪方面的学习?
既然我们已经了解了在系统中建立适应性的层次,那么我们能适应学生学习的哪些方面呢?鉴于直觉会告诉我们学生可以有不同的学习风格,并将不同的情绪与学习内容联系起来,我们应该把自己局限于学生的知识吗?以下是我们可以适应的一些可能的元素,可以使用的 DS/ML 技术,以及迄今为止围绕它们的基于研究的结论。
先验知识和知识增长

照片由 Charl Folscher 在 Unsplash 上拍摄
学习者在目标领域有不同程度的先验知识和经验,所以一课适用于所有人的方法是行不通的。因此,智能教学系统需要能够评估学生的知识,并相应地调整教学。根据目前的研究,这可以在所有三个适应性水平上有效地完成
设计循环调整:对课程互动和表现数据进行分析,以确定洞察力,如缺少学生需要接受培训的先决条件,学生可能基于不完整的指导做出不正确的假设等。教育数据挖掘领域处理不同尺度的多维分析。
任务循环适应:在这一级,我们试图根据我们迄今为止对学习者的知识建立的模型来预测下一个最好的任务。一种流行的方法使用认知掌握进行任务选择和贝叶斯知识追踪模型来确定什么是理想的下一个任务,以最大限度地学习,同时最小化多余的努力。
步骤循环适应:在这个级别,我们试图在学习者完成任务时向他们提供实时反馈,例如中间步骤的正确性。研究表明,这有助于减少学习者的不确定性,从而提高学习者的效率。实现这一点的一个简单而流行的方法是基于规则的认知建模。
策略和错误

Felix Mittermeier 在 Unsplash 上拍摄的照片
学习者在完成一项任务时可能会使用不同的策略来实现相同的目标(比如使用不同的方法来解决一个数学问题),这可能会导致学习者面临不同的错误。(如计算错误、单位转换错误、不正确的公式等)研究表明,ITS 可以在设计和步骤循环级别适应这样的学习者偏好。目前,没有任何结论性的研究表明任务循环适应这一目标可以提高学习。
设计循环调整:在这一阶段,我们试图确保学生能够使用不同的策略,但同时需要足够的中间步骤,也称为“脚手架”,这样我们就可以跟踪他们正在使用什么策略,而无需明确询问他们。根据互动和表现数据,教师可能会决定改变任务周围的脚手架,以允许更多的策略和尽量减少错误。知识组件建模是一种解决这种设计循环适应性的流行方法。
步骤循环适应 s:在这一阶段,我们试图解释错误,或错误步骤的影响,并根据我们迄今为止推断的学生策略提供下一步的提示。动态贝叶斯网络和强化学习是构建步进循环自适应的常用方法。
情感和动机
学习者可能会经历不同的情绪,如厌倦、困惑、沮丧、投入/心流,也称为“情感状态”,这可能会影响他们对课程内容的看法和总体表现。因此,智能交通系统必须能够检测情感状态,并促进有利于学习的情感状态。研究表明,它能适应所有三个适应水平的情感状态。
设计循环适应:在这一级,分析互动和表现数据,以了解有利于学习的情感状态。它旨在促进有利于学习的情感状态。例如,教育数据挖掘通常用于识别与“游戏系统”相关的学习者和行为,其中学习者表现出不参与(通过使用计算机视觉检测到的面部提示来识别),并且可能利用实时反馈来提供例如关于中间步骤或下一步提示的正确性反馈。教师可以设计机制来阻止这种情况。
任务循环适应:在这个阶段,我们的目标是选择下一个任务,它不仅能优化学习效果,还能产生有利于学习的情感状态。例如, A/B 测试表明,围绕学生兴趣(如体育/娱乐/艺术等)量身定制任务环境会带来更好的学生参与度和整体表现。
循环适应:在这个级别,我们的目标是在任务中提供支持,促进积极的情感状态。这可以是在提供暗示、提醒让走神的学生回来或激励信息时的移情对话。研究和 A/B 测试表明,这使学生以更好的方式看待它,并取得更好的学习成果。
自我调节学习(SRL)

照片由 Josefa nDiaz 在 Unsplash 上拍摄
学习者采取一些自我激励的行动来掌握概念,如设定目标、对比不同的策略和评估自己的表现,以了解自己的优势和劣势。所有这些行为都属于 SRL 范畴,它解释了学习结果的显著差异。研究表明,SRL 可以在设计回路和步进回路级别进行调整。关于任务循环适应的研究尚无定论。
设计循环调整:在这一层面,我们利用教育数据挖掘和用户调查来了解 SRL 行为,从而实现最佳学习。然后,教师在智能交通系统中设计机制,以促进成功的 SRL 行为。例如,研究和 A/B 测试表明,让学生解释他们的答案会带来更好的学习效果。
分步循环适应学生:在这一阶段,我们利用领域和学习者模型来评估学生在任务中的熟练程度,并提供适应性信息以鼓励 SRL 活动。例如,根据学生与任务的互动——所用时间、尝试次数、迄今为止使用的提示等,可以识别出正在努力的学生。可以向这样的学生提供适应性信息,以在继续之前反映和解释他们迄今为止所做的事情。研究表明,这种分步循环适应性导致更好的学习结果,并促进“为未来的学习做准备”。
学习风格

布鲁克·卡吉尔在 Unsplash 上的照片
学习者对自己的学习方式有各种各样的偏好。直觉告诉我们,根据学生的学习风格调整内容会提高学习效果。然而,迄今为止的研究并没有提供任何适应性水平相同的证据。
因此,总结研究,我们可以创建一个我们可以适应什么与我们适应的水平(设计/任务/步骤)的概述,在文献中称为适应性网格。(如下图所示)

作者创建的图像
结论
在这篇文章中,我们讨论了什么是适应性学习,智能交通系统是什么样子,我们可以在不同的水平上适应智能交通系统,以及我们可以适应智能交通系统的不同学习元素。
对于那些希望深入这个相对较新的领域的人来说,除了特定领域的技术(认知建模、知识组件建模、ACT-R),所需的关键 DS/ML 知识包括强化学习、贝叶斯建模和 A/B 测试。
参考文献
人工智能表简介
原文:https://towardsdatascience.com/introduction-to-ai-tables-a719251e1a58
使用 MindsDB 将 ML 集成到数据库中

在 Unsplash 上由 Hitesh Choudhary 拍摄的照片
在这篇文章中,我们将涉及人工智能表的主题。如今,机器学习是一个很好的工具。我们可以将它应用于许多不同的任务,从图像识别到自然语言处理。
然而,ML 模型在现实世界中最重要的一个用例是对我们已经存储在数据库中的表格数据进行预测。那么,如果我们可以直接将这个模型应用到桌子上呢?这就是人工智能桌子。
人工智能表
人工智能表将机器学习模型直接集成为数据库中的虚拟表。这允许我们创建可以用 SQL 语句查询的预测器。
通过这样做,我们简化了很多 ML 生命周期,因为许多操作都是在数据库内部完成的,我们不需要在数据库之外的地方处理数据。
但是,为了更好地理解它们是如何工作的,在本文中,我们将制作一个完整的示例,说明如何使用直接在数据库中训练的模型来创建、训练和预测。
为此,我们将使用一个名为 MindsDB 的工具。MindsDB 是一个数据库内的 ML 工具,它帮助我们扩展 SQL 语言,以便可以创建、查询和维护 ML 模型,就像它们是数据库表一样。让我们看看它的实际效果。
示例:人口普查收入预测
本例中使用的所有代码都可以在这个库中找到。
数据
对于这个例子,我们将使用“人口普查收入”数据集。目标是根据人口普查数据预测一个人的收入是否超过 5 万美元/年。数据集可以在这个链接中找到,从 UCI ML 库中提取。
下载的文件包含 3 个文件:
- 成人数据:包含 32561 条用于训练的人口普查数据记录的文件。
- 成人测试:包含 16281 条测试用人口普查数据记录的文件。
- 成人名称:包含数据集描述、信息、许可证等的文件。
数据上的每条记录由 14 列组成,在这里我们可以找到诸如年龄、工作类别、教育程度等属性。最后一列对应于目标列,年薪是否超过 50K 美元。
数据预处理
我们将对数据进行一些预处理,使整个过程更容易理解。首先,让我们看看使用 pandas 库的原始数据集。
Shape: (32561, 15)

df 的前 5 行,按作者
每列的名称列表可以在 aduls.name 文件中找到,同时还有数据集的其他信息。
正如我们所见,我们有一个普通的熊猫数据框架,其中有一些分类数据和一些数字数据。我们可以处理分类数据、标准化值、清理数据集等。但是这个模型将按原样处理数据,并且因为这篇文章的目的是解释 AI 表的概念,我们现在将数据留下。
我们要更改的唯一一列是最后一列。我们将用值' < =50K '和' < 50K '分别代替 0 和 1,只是为了让问题看起来像一个正常的分类问题。
这是我们需要在数据集中做的所有预处理。现在我们可以将数据帧导出到一个 csv 文件中。我们很快就会需要它。
该文件名为' data.csv' ,我们不会在新文件中保留索引列。
对于测试集,我们将做同样的事情。我们将保留最后一列,因为在进行预测时,我们将使用预测值创建一个全新的列。然后,我们将使用该列和“目标”列来获取关于模型性能的指标。
Shape: (16281, 15)

测试数据的前 5 行,按作者
在这种情况下,当加载 dataframe 时,我们需要使用skip prows属性,因为测试集的第一行本身不是记录。
现在我们也可以将数据帧保存在一个 csv 文件中。我们将它命名为' test.csv' 。
MindsDB
是时候拿出人工智能表,创建我们的预测模型了。在本文中,我们将使用 MindsDB cloud,但是您可以将它安装在您的系统上。你可以在他们的文档中找到所有的信息。
首先,我们需要一个帐户并登录到云中。

我们将有一个编辑器来进行普通的 SQL 查询。现在我们必须导入我们在最后一步中生成的数据。为此,请转到“添加数据”页面,选择“文件”选项卡,然后转到“导入文件”。

导入文件页面
这里我们要导入之前生成的两个 csv 文件( data.csv 和 test.csv )。我们将把它们分别命名为“训练数据”和“测试测试”。
导入文件后,我们可以通过在编辑器中运行以下查询来检查它们是否被正确上传。
Tables_in_files
-------------------
test_data
train_data
我们可以看到两个表现在都在云中。从现在开始,我们将使用 SQL 查询来与数据交互。例如,我们可以使用以下 SQL 命令检查训练数据的前 10 条记录。
创建预测器
现在我们已经加载了数据,是时候创建一个预测器了。我们将使用' create predictor 语句。
- salary_predictor 是预测者的名字。
- 文件是上传文件的默认表名。
- 目标是我们要预测的列的名称。
这个查询将为我们的数据创建一个预测器。现在必须根据数据训练预测器。我们可以用下面的查询来检查它的状态。
我们创建的所有预测值都将存储在 mindsdb.predictors 表中。
查询的输出如下所示。

我们可以注意到它正处于训练阶段,还有关于预测器的其他数据。
完成培训过程大约需要 5-10 分钟。完成后,我们可以再次运行前面的查询来查看结果。

我们现在可以看到,状态为'完成,精度为 0.832。这意味着我们的预测器在训练数据中有大约 83%的准确性。
就是这样!我们已经使用 SQL 查询直接在表中训练了一个模型。现在我们可以用测试数据测试预测器。
做预测
MindsDB 给了我们两种可能的预测方法。第一个是运行一个 SQL 查询,查询参数中有一个新记录。如果我们想要预测准时的新数据,这是完美的。
另一种方法是对我们数据库中的大量数据进行批量预测。这是我们的场景,因为我们有一个包含新记录的完整文件( test_data ),一个接一个地进行会花费很长时间。
我们可以使用以下查询对一组数据进行预测。
通过 SELECT 语句,我们将使用数据中所有可用的记录字段。然后,我们将使用JOIN语句,使用我们刚刚训练的模型进行预测。最后,我们将使用预测生成一个名为' predicted_salary '的新列。
如果我们运行该查询,它将输出一个新的 dataframe,其中包含一个额外的预测列。让我们用 python 来评估这个模型。在输出上方的右侧,有一个将结果导出到一个中的按钮。csv 文件。我在图中用绿色标出了它。

点击它,它将下载查询的结果。我将把文件名改为' predictions.csv ',这样下面的代码将使用这个名称。现在让我们用熊猫来检验模型的性能。记住,基于训练过程,预测器的准确度大约是 83%。
我们将使用 seaborn 来可视化结果,使用 confusion_matrix 来生成模型的指标。
(16281, 16)
现在我们打开文件,检查输出形状有 16 列,比原来的数据帧多了一列。请记住,这是因为该数据有一个额外的列用于预测标签。
让我们使用我们感兴趣的列,并计算混淆矩阵。
现在,我们有了数组格式的指标,让我们为它们创建一个可视化。

作者对混淆模型的度量
我们还可以看看指标。
Precision: 0.8420587052673905
Recall: 0.9418907978771252
F1: 0.8891813858695653
正如我们所观察到的,测试集中的精度约为 84%,非常接近预测指标给出的精度。
记住所有的代码都在这个库里。
结论
所以本教程到此为止。我们已经看到什么是人工智能表,我们运行一个例子来看看它是如何工作的。但这并不是这个工具能做出的所有可能性。我在这里链接了 MindsDB 的官方文档,所以你可以看看所有其他的可能性。
我建议你用你的数据集试试这个,或者尝试做一个新的实验。此外,您可以创建回归模型,而不是分类模型。我留给你们的另一个想法是提高这个模型的性能。您可以尝试清理和处理训练数据,并检查预测器是否改善了其度量。这可能是一个有趣的挑战!
我希望你发现这个教程是有用的。如果你有任何进一步的问题,随时留下评论。下次见!👋🏻
Apache Hadoop 分布式文件系统简介
原文:https://towardsdatascience.com/introduction-to-apache-hadoop-distributed-file-system-99cb98d175c
关于这个 Hadoop 组件,您需要了解的一切

马丁·比约克在 Unsplash 上的照片
Apache HDFS 是一个分布式文件系统,用于存储大数据领域的大量数据,并将其分布在不同的计算机上。该系统使得 Apache Hadoop 能够以分布式方式跨大量节点(即计算机)运行。
什么是 Apache 框架 Hadoop?
Apache Hadoop 是一个软件框架,可以用来快速处理分布式系统上的大量数据。它具有确保稳定和容错功能的机制,因此该工具非常适合在大数据环境中进行数据处理。软件框架本身由四个部分组成。
Hadoop Common 是各种模块和库的集合,支持其他组件并使它们能够协同工作。其中,Java 归档文件(JAR 文件)存储在这里,这是启动 Hadoop 所需要的。此外,该集合允许提供基本服务,如文件系统。
Map-Reduce 算法 源自谷歌,有助于将复杂的计算任务划分为更易于管理的子流程,然后将这些子流程分布到多个系统中,即横向扩展。这大大减少了计算时间。最后,子任务的结果必须再次组合成整体结果。
另一个资源协商器(YARN) 通过跟踪计算机集群中的资源并将子任务分配给各个计算机来支持 Map-Reduce 算法。此外,它还为各个进程分配容量。
Apache Hadoop 分布式文件系统(HDFS) 是一个用于存储中间或最终结果的可伸缩文件系统,我们将在本文中详细讨论。
我们需要 HDFS 做什么?
在集群中,HDFS 分布在多台计算机上,以快速高效地处理大量数据。这背后的想法是,大数据项目和数据分析基于大量数据。因此,应该有一个系统能够批量存储数据并快速处理数据。HDFS 确保存储数据记录的副本,以便能够应对计算机故障。
根据自己的文档 , Hadoop 使用 HDFS 的目标如下:
- 从硬件故障中快速恢复
- 启用流数据处理
- 巨大数据集的处理
- 易于迁移到新的硬件或软件
Hadoop 分布式文件系统的结构
Hadoop 分布式文件系统的核心是将数据分布在不同的文件和计算机上,以便快速处理查询,并且用户不会等待很长时间。为了确保群集中单台机器的故障不会导致数据丢失,在不同的计算机上进行有针对性的复制以确保恢复能力。
Hadoop 一般按照所谓的主从原则工作。在计算机集群中,我们有一个承担所谓主节点角色的节点。在我们的示例中,这个节点不执行任何直接计算,而只是将任务分配给所谓的从节点,并协调整个过程。从节点依次读取书籍并存储词频和词分布。
这个原理也用于数据存储。主节点将数据集中的信息分发到不同的从节点,并记住它在哪些计算机上存储了哪些分区。它还冗余地存储数据,以便能够弥补故障。当用户查询数据时,主节点然后决定它必须查询哪些从节点,以便获得想要的信息。
Apache Hadoop 分布式文件系统中的主服务器称为 Namenode。从节点就是所谓的 datanodes。从下到上,示意结构可以理解如下:
客户端将数据写入不同的文件,这些文件可以位于不同的系统上,在我们的示例中是机架 1 和机架 2 上的 datanodes。集群中的每台计算机通常有一个 datanode。它们主要管理计算机上可供它们使用的内存。几个文件通常存储在内存中,这些文件又被分成所谓的块。
名称节点的任务是记住哪些块存储在哪个 datanode 中。此外,他们管理文件,并可以根据需要打开、关闭和重命名文件。
datanodes 又负责客户端(即用户)的读写过程。在发生查询时,客户端也从它们那里接收所需的信息。同时,datanodes 还负责数据的复制,以保证系统的容错性。
Hadoop 分布式文件系统有什么优势?
对于许多公司来说, Hadoop 框架作为数据湖也变得越来越有趣,即由于 HDFS,作为大量数据的非结构化存储。各种因素在这里起着决定性的作用:
- 在分布式集群中存储大量数据的能力。在大多数情况下,这比在一台机器上存储信息要便宜得多。
- 高容错能力,因此系统高度可用。
- Hadoop 是开源的,因此可以免费使用,源代码可以查看
这些要点解释了 Hadoop 和 HDFS 在许多应用程序中的日益普及。
这是你应该带走的东西
- HDFS 是一个分布式文件系统,用于存储大数据领域的大量数据,并将其分布在不同的计算机上。
- 它是 Apache Hadoop 框架的一部分。
- 主节点将数据集分成更小的分区,分布在不同的计算机上,即所谓的从节点。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !此外,媒体允许你每月免费阅读三篇文章。如果你想让无限制地访问我的文章和数以千计的精彩文章,请不要犹豫,通过点击我的推荐链接:https://medium.com/@niklas_lang/membership每月获得5 美元** 的会员资格***
** **
自动编码器简介
原文:https://towardsdatascience.com/introduction-to-autoencoders-b6fc3141f072
自动编码器
如何使用 Tensorflow 简化您的数据

我想象你有一个数据集( X , y ),其中包含了大量的特征,你想用它们来解决一个任务,比如回归或分类。虽然拥有更多的观察值总是更好,但是在一个模型中包含太多的特性可能会适得其反,无论是对于可解释性还是甚至是模型的性能。考虑这个简单的例子:
import numpy as np
np.random.seed(0)
X = np.random.randn(1000, 900)
y = 2*X[:, 0] + 1 + 0.1*np.random.randn(1000)
在这里,我们创建了一个数据集( X , y ),包含 1000 个样本,每个样本有 900 个特征 x ₀,…, x ₈₉₉。我们想知道的事实是 y = 2 x ₀ + 1,由于误差也很小,线性回归应该完全达到接近 1 的 r 。然而,进行交叉验证会产生
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score
print(cross_val_score(LinearRegression(), X, y))
# Output:
# [0.88558154 0.87775961 0.87564832 0.86230888 0.8796105]
看不见的数据上的 r 约为 0.87,离 1 相当远。当将模型限制为仅使用前 10 个变量时,通过进行相同的分析,我们可以更清楚地看到这有多糟糕:
print(cross_val_score(LinearRegression(), X[:, :10], y))
# Output:
# [0.99701015 0.99746395 0.99780414 0.99752536 0.99745693]
我们可以看到,丢弃特征将性能提高到大约 0.997。这意味着即使像线性回归这样简单的算法也可能淹没在特性中。
有几种技术可以减少特性的数量,比较流行的有
- 仅仅根据诸如单变量分析(即与目标 y 的相关性)或重要性分数(p-值、基于树的算法的特征重要性、Shapley 值等)的规则来丢弃一些特征,以及
- 主成分分析 (PCA),使用特殊的线性映射将数据转换到低维空间(即,将数据矩阵 X 乘以另一个矩阵 W )。注意这里我们不需要y。
在本文中,我们将了解到类 对 PCA 的另一种推广方法:内核 PCA 。
开玩笑,我指的当然是自动编码器!(不过内核 PCA 是一个有效的选择。)
自动编码器
那么,这些神秘的自动编码器是如何工作的,为什么我们首先要关心呢?让我们来找出答案。
直觉
通常,手边的数据包含了 T4 冗余,可以很容易地压缩而不会丢失太多信息。想象一个数据集,它具有以厘米为单位的高度和以米为单位的高度。这两个要素包含相同的信息,但是如果它们都有不同种类的噪声,可能会影响模型性能。所以,还是选这两个中的一个比较好。
虽然这种情况很明显,但自动编码器可以找到更细微的冗余,并将其压缩掉。我们希望的是,自动编码器中间所谓的信息瓶颈(潜在空间,见下文)不会过滤掉必要的信息,而只是冗余信息。一个简单的检查是通过查看所有的 x 和 x 之间的重建误差。一会儿就变得更具体了。
定义
对于什么是自动编码器,没有一个真正的数学定义,至少我找不到。不过,我可以给你一个足够好的直觉。
自动编码器由两个功能 e (编码器的简称)和 d (解码器的简称)组成,其行为如下:
- e 将一个具有 n 特征的观测值 x 作为输入,并将其映射到一个维度为 m ≤ n 的低维向量 z ,称为 x 的潜在向量。
- d 取潜在向量 z 并再次输出一个尺寸为 n 的x’。
另外,我们希望 x 和 x 接近,即x≈x’,即重建误差应该很小**。用通俗的语言来说:
编码器压缩原始观察值 x 。解码器再次解包。这个打包和拆包的过程不要太扭曲 x(平均)。

图片由作者提供。
像 zip 和 rar 这样的压缩工具也试图达到同样的效果,但是有一个更严格的条件:x = x’。 PNG 是另一种无损格式,虽然 jpeg 或 mp3 等格式并非无损,但足够好让人类看不出区别,至少如果压缩不是太激进,即 m 不是太小。
在我们转向通用自动编码器之前,让我们重复一下 PCA 在高层次上做什么——以及它失败的时候。
何必呢?
好的,我们知道拥有太多的特性可能是一件消极的事情。我们还谈到了 PCA 能够减少特征的数量。然而,PCA——作为线性变换——具有有限的表达能力。提醒一下 PCA 是如何工作的:

图片由作者提供。
在 autoencoders 的语言中, 左下角的图像显示编码 (它只是一维而不是二维),右下角的 图像显示解码 (回到原来的二维)。两个顶部图像都包含原始输入数据。
如果数据接近于在一个维度为 m 的线性子空间(即直线、平面、超平面)中,具有 m 分量的 PCA 工作得很好。但是,如果不这样做,我们可能会丢失许多关于原始数据的信息,正如我们在这里看到的:

图片由作者提供。
好吧,如果线性变换的简单性是瓶颈,让我们使用更复杂的编码器和解码器!实现这一点的一个简单方法是使用神经网络,所以让我们开始吧。
注: 通常,当人们谈论自动编码器时,他们指的是 基于神经网络的自动编码器 。但是这并不是唯一的选择:只要你有一个解码器和一个编码器,一切都是允许的。例如,你可以根据下面的决策树来检查一个奇特的自动编码器。然而,在本文中,我将坚持使用传统的自动编码器。**
Tensorflow 中的实现
编码时间到了!首先,让我们做一些基本的事情,用以下方式定义一个具有 4 个特征的数据集 X :
import tensorflow as tf
tf.random.set_seed(0) # keep things reproducible
Z = tf.random.uniform(shape=(10000, 1), minval=-5, maxval=5)
X = tf.concat([Z**2, Z**3, tf.math.sin(Z), tf.math.exp(Z)], 1)
如果你想一想,你能如何压缩 X 的维度?用更少的特征重构 X 有什么必要?
嗯,我们只需要 Z ,即一个单一特征。 X 并不包含比 Z 更多的信息,我们只需对 Z 应用某种确定性函数就可以得到 X 的特征。
编码器可以计算出如何找到函数的逆,即从 x = ( z , z , sin ( z ), exp ( z ))到z,解码器可以学习取一个数 z 并将其转化为
你有没有注意到我在这里是如何使用这个词的?对我们来说,这将是显而易见的解决方案,因为我们知道如何从 Z 生成 X 。然而,自动编码器不知道这一点,可能会找到另一种方法来压缩数据。比如它可以想办法把 x = ( z , z , sin ( z ), exp ( z ))压缩成 z - 3,解码器可以重新学习解码这个,一样好。
好的,所以我们应该能够建立一个只有一个潜在维度的性能良好的自动编码器。我们可以通过以下方式做到这一点
*class AutoEncoder(tf.keras.Model):
def __init__(self):
super().__init__()
self.encoder = tf.keras.Sequential([
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dense(1), # compress into 1 dimension
])
self.decoder = tf.keras.Sequential([
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dense(4), # unpack into 4 dimensions
])
def call(self, x):
latent_x = self.encoder(x) # compress
decoded_x = self.decoder(latent_x) # unpack
return decoded_x
tf.random.set_seed(0) # keep things reproducible
ae = AutoEncoder()
ae.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
loss='mse'
)
ae.fit(
X, # input
X, # equals output
validation_split=0.2, # prevent overfitting
epochs=1000,
callbacks=[
tf.keras.callbacks.EarlyStopping(patience=10) # early stop
]
)
# Output:
# [...]
# Epoch 69/1000
# 250/250 [==============================] - 0s 1ms/step - loss: # 0.0150 - val_loss: 0.0276*
看起来不错,模型可以学习底层结构!我们还可以通过以下方式检查自动编码器产生的最大误差
*print(tf.reduce_max(tf.math.abs(X-ae(X))))
# Output:
# tf.Tensor(0.89660645, shape=(), dtype=float32)*
不错,考虑到有些功能要上百个。现在我们知道了自动编码器的工作原理,让我们做一些更有趣的事情。
压缩数字
自动编码器的一个经典例子是使用手写数字的 MNIST 数据集。让我们通过以下方式获取数据集
*(X_train, _), (X_test, _) = tf.keras.datasets.mnist.load_data()
X_train = X_train.reshape(-1, 28, 28, 1) / 255\. # value range=[0,1]
X_test = X_test.reshape(-1, 28, 28, 1) / 255\. # shape=(28, 28, 1)*
画几个数字:
*import matplotlib.pyplot as plt
for i in range(3):
plt.imshow(X_train[i])
plt.show()*
*


MNIST 数据集中的数字。图片由作者提供。*
正如我们所看到的,数据集由手写数字组成,确切地说是 28x28 像素。由于每个像素都是一个特征,所以我们面对的是一个 2828= 784 维的数据集,对于一个图像数据集来说,这甚至非常低。*
我们现在将为这个数据集构建一个自动编码器。但是我们为什么要成功呢?答案是手写数字是所有潜在 28x28 像素图片的非常小的子集。它们有非常特殊的像素模式。举例来说,尽管以下图像是 28x28 像素的图像,但它们绝不会被视为数字:
*


不是数字。图片由作者提供。*
但是我们需要多少个维度来表达 MNIST 数据集呢?不幸的是,这个问题没有通用的答案。通常,我们试图使维度尽可能小,以使重建(解码)足够好。
我们现在可以再次构建另一个简单的前馈自动编码器,我鼓励您这样做。我们要做的是使用卷积层建立一个自动编码器,因为我们在这里处理图像数据。这些类型的自动编码器被方便地称为卷积自动编码器。开门见山,对吧?
注意,图像的像素值在 0 和 1 之间,用于更快和稳定的训练,并且单个图像的形状为(高* = 28,宽 = 28,通道 = 1),用于卷积层工作。*
*from tensorflow.keras import layers
class ConvolutionalAutoEncoder(tf.keras.Model):
def __init__(self):
super().__init__()
self.encoder = tf.keras.Sequential([
layers.Conv2D(4, 5, activation='relu'),
layers.Conv2D(4, 5, activation='relu'),
layers.Conv2D(1, 5, activation='relu'),
])
self.decoder = tf.keras.Sequential([
layers.Conv2DTranspose(4, 5, activation='relu'),
layers.Conv2DTranspose(4, 5, activation='relu'),
layers.Conv2DTranspose(1, 5, activation='sigmoid'),
])
def call(self, x):
latent_x = self.encoder(x)
decoded_x = self.decoder(latent_x)
return decoded_x
tf.random.set_seed(0)
ae = ConvolutionalAutoEncoder()
ae.compile(optimizer='adam', loss='bce')
ae.fit(
X_train,
X_train,
batch_size=128,
epochs=1000,
validation_data=(X_test, X_test),
callbacks=[
tf.keras.callbacks.EarlyStopping(patience=1)
]
)
# Output:
# [...]
# Epoch 12/1000
# 469/469 [==============================] - 91s 193ms/step - loss: # 0.0815 - val_loss: 0.0813*
我们可以通过它来查看潜在空间有多大
*print(ae.summary())*
我们看到第一层(即编码器)的输出形状为(None,16,16,1 ),这意味着输出是 16 * 16 * 1 = 256 维,也可以解释为单通道的 16x16 像素图像。
在我们继续之前,我们还可以手动检查一些图像重建:
*n = 10
decoded_images = ae.predict(X_test[:n])
latent_images = ae.encoder(X_test[:n])
plt.figure(figsize=(20, 4))
for i in range(n):
ax = plt.subplot(3, n, i + 1)
plt.imshow(X_test[i])
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax = plt.subplot(3, n, i + 1 + n)
plt.imshow(latent_images[i] / tf.reduce_max(latent_images[i]))
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax = plt.subplot(3, n, i + 1 + 2*n)
plt.imshow(decoded_images[i])
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()*

顶行:原始,中行:压缩,底行:重建。图片由作者提供。
整体看起来还过得去!它到处都有一些问题,例如 2,但建立一个更好的网络架构可能会使它变得更好。我们还可以看到,原始图像的潜在版本仍然非常类似于原始数字,只是分辨率更差,因为它是 16x16 而不是 28x28 像素。虽然情况并不总是这样,但是在这里,autoencoder 碰巧发现这是一个很好的压缩。
我发现有趣的是,自动编码器了解到图像的边界不包含有用的信息。它只是放大数字,让它们填充完整的图像,这很有意义——我可能会像人类一样做同样的事情。然而,和神经网络一样,它不一定是这样的。在这里,这只是一个很好的小巧合。
更多架构
我们现在已经看到了普通的自动编码器,以及卷积自动编码器。你可能已经猜到,通过改变自动编码器的结构,我们也可以创建 LSTM 自动编码器或序列到序列自动编码器、变压器自动编码器等等。
还有稀疏自动编码器,如果编码器一次使用太多的潜在维度,它会惩罚编码器。您可以通过activity_regularizer关键字将活动正则化添加到图层中。
尽管主要思想总是相同的:获取一个输入并使其变小。然后尽你所能把它放大一倍,基本上就像每个犯罪系列里一样(这是真的!):

图片由信任号在https://imgur.com/gallery/zVq9W7u上。
另一种值得注意的我非常喜欢的自动编码器是变体自动编码器。它不仅能够重建数据,甚至能够创建新数据,这使其成为创成式模型。使用它们,您可以创建催眠图片,如

https://www.tensorflow.org/tutorials/generative/cvae
结论
我们已经看到,数据可能带有大量冗余。这增加了处理所述数据所需的内存和计算能力,甚至可能降低模型的性能。通常,我们可以通过删除一些功能或使用 PCA 等方法来摆脱困境。
然而,这些方法可能过于简单,无法从数据中恰当地提取相关信息——它们存在不足。为了解决这个问题,我们可以使用自动编码器,它可以按照我们需要的复杂程度来转换数据。大多数时候,我们使用神经网络构建自动编码器,但也存在其他方法,例如使用决策树的。
然后,我们看到了如何用 Tensorflow 用几行代码构建一个普通的卷积自动编码器。您还看到了更多的自动编码器类型,这取决于您如何定义您的编码器和解码器。
现在,您可以开始自己探索自动编码器,使用它们,看看您是否能从它们中受益,解决您的问题。玩得开心!
我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!
作为最后一点,如果你
- 想支持我多写点机器学习和
- 无论如何都要计划获得中等订阅量,
为什么不做 通过这个环节 ?这将对我帮助很大!😊
透明地说,给你的价格不变,但大约一半的订阅费直接归我。
非常感谢,如果你考虑支持我的话!
有问题就在LinkedIn上写我!
Python 中使用描述性统计和统计图进行数据处理的介绍
Python 中测试数据假设(MCAR、MAR、MNAR、集中趋势、偏斜度和异常值)的完整实践指南

贾斯汀·摩根在 Unsplash 上的照片
数据可视化和统计
我们围绕数据的大部分工作涉及 70%的描述性分析,10%的探究性分析,剩下的 20%是预测性和规范性分析。描述性分析可以回答市场上正在发生的事情。例如,如果我们为一家零售店工作,我们希望确定他们的月销售额、客户流量、增长、净利润、业务来源(比如新客户和现有客户)以及多个其他 KPI。所有这些 KPI 都可以使用任何商业智能(BI)工具进行计算和可视化表示,如 Power BI、Tableau、Qlik Sense,甚至可以使用 Excel 和 PowerPoint 的组合。那么我们为什么需要 Python 及其可视化能力呢?
考虑到可再现的包和情节的限制,在 Python 中创建美观的可视化是具有挑战性的。Matplotlib(使用 Pyplot)、Seaborn 和 Plotly (plotly。express)可以用 Python 生成大部分统计图。这些软件包的主要限制在于它们格式化的灵活性,因此在描述性分析方面并不优于 BI 工具。虽然 Seaborn 不会自动生成数据标签,但 Plotly 并不支持 Python 中所有可能的可视化。Matplotlib 形成了所有可视化的主干,并在三层架构系统上运行,“后端层”、“艺术家层”和“脚本层”允许用户控制布局和格式。尽管如此,它也带来了复杂编码的巨大代价,而这是许多用户所避免的。
统计学和 Python 适合在哪里?
Python 提供了一些包,允许用户在构建任何预测模型之前研究数据属性并采取适当的措施。线性回归、逻辑回归、朴素贝叶斯分类器等统计模型,甚至基于聚类的任务都会对数据做出假设。在建立模型之前,我们可以使用图来验证这些假设。这些图使用描述性统计作为基础。我们将一点一点地讨论这个问题。你可以在拥有公共领域许可证的 Kaggle 中找到用于此分析的数据集,这意味着每个人都可以访问它。在本文中,我们将涵盖以下概念:
- 缺失值和缺失值的相关性-测量缺失值的随机性。
- 使用数据分布的数据偏斜度
- 离群值和图基阶梯
- 皮尔逊相关系数—了解多重共线性
这项分析中使用的数据由以下属性组成——亿万富翁的“姓名”、“净值”、“国家”、“来源”、“排名”、“年龄”和“行业”。
#---------------------Importing packages for visualization----------------import pandas as pd #---Pandas for dataframe
import numpy as np #---Numpy for manipulation; Not used in this analysisimport seaborn as sns #---Seaborn for graphs
import matplotlib.pyplot as plt #---Pyplot for canvas (e.g. figure size, title, x and y axis ranges etc.)sns.set(color_codes=True)%matplotlib inline#With this backend, the output of plotting commands is displayed inline within frontends like the Jupyter notebook,
#directly below the code cell that produced it. The resulting plots will then also be stored in the notebook document.data_MM=pd.read_csv('Billionaire - 2021.csv')data_MM.head()

图一。展示了数据集中的前五条记录和相关属性(列名)。图片由作者使用 Jupyter 笔记本制作。
1.了解缺失值
缺失值通常是指在数据收集过程中没有引入或丢失的属性。多种原因可导致缺失值,包括手动数据输入程序、设备错误和不正确的测量。Python 将缺失值视为 NA/Null/NaN,并自动忽略它们进行描述性分析。例如,当我们在原始数据上创建一个条形图来按行业分析亿万富翁的数量时,在创建计数图时,数据中任何缺失的名字都将被忽略。但是,假设使用相同的缺失值作为输入来构建聚类或回归模型。在这种情况下,Python 将抛出一个错误,因为大多数内置的统计建模包不能处理缺失值。我们可以使用以下内容来检查缺失值:
data_MM.info()data_MM.isnull().sum()
可以使用 missingno 包创建这些缺失值的可视化表示。
!pip install missingnoimport missingno as msplt.title("Missing Values", fontsize=20);ms.bar(data_MM);

图二。说明了数据中缺失值按属性的分布情况。主 X 轴提供非缺失值的百分比,而次 X 轴提供非缺失记录的计数。图片由作者使用 Jupyter 笔记本制作。
正如您所看到的,我们只有一个属性“年龄”,它缺少一些条目。
- 第一种方法是丢弃属性中缺少值的记录。删除缺失值较高的属性属于此类别。然而,这种技术的选择取决于从分析中移除的数据的百分比。如果百分比较低,则用户可以要求删除缺失的属性,否则需要某种形式的插补
- 另一种方法是使用最大似然法,对完整数据部分的模型参数进行估计,然后用于抽样估算。这些包括期望最大化、多重插补、贝叶斯主成分分析等等
- 最后,缺失值插补是一类旨在用估计值填充缺失值的程序。在大多数情况下,数据集的属性并不是相互独立的。因此,通过识别属性之间的关系,可以确定丢失的值。这还涉及领域知识和业务规则,它们可以用来纠正缺失的值
下面的可视化旨在检查变量/列中的随机样本或间隔值是否缺失。如果由于另一个变量缺少值而导致缺少值(例如,某个特定记录的年龄和姓名都缺少),或者某个特定属性中的值独立于另一个属性而缺少(例如,在我们的数据集中,只有年龄缺少值,而所有其他属性都完整)。遗漏的类型包括:
- 完全随机缺失(MCAR) :缺失与观察值或缺失值无关
- 随机缺失(MAR): 缺失与其他观测数据之间存在系统关系,但缺失数据不存在系统关系
- 非随机缺失(MNAR): 缺失和它的值有关系,缺失或非缺失
#---------------------------------------Plotting data using msno----------------------------ms.heatmap(data_MM,figsize=(10,8), fontsize=12);
plt.title("Missing Number Correlation Plot");

图 3。说明数据中缺失值之间的相关性。我们只有缺失条目的“年龄”;因此,我们看不到上面的相关性,也就是说,我们可以断定“年龄”的值完全是随机缺失的。图片由作者使用 Jupyter 笔记本制作。
对于随机缺失或完全随机缺失的值,像平均值和中值这样的简单插补是最佳策略,这取决于偏斜度。对于偏斜的数据,最好使用中位数来抵消异常值的影响,而对于正态分布的数据,可以使用平均值。当数据随机缺失或者与另一个属性密切相关的属性中的值没有缺失时,也可以使用 K 最近邻等高级技术。对于 MNAR,即非随机缺失的值,一种流行的技术是从非缺失分布中采样值,直到缺失值处理后的数据分布与原始分布的分布匹配。
2.数据偏斜度
数据偏斜度表示数据值围绕平均值不对称的程度。测量偏斜度的一种方法是研究分布的平均值和中值。
- 均值>中位数:正分布还是右偏分布
- 均值=中位数:零偏斜或对称分布
- 平均值< Median: Negative or left-skewed distribution

图 4。说明了偏斜度的公式。Xi 是数据点,X 条是数据的平均值,西格玛是标准偏差,n 是记录数。
data_MM['Age'].skew()
Output: 0.045
data_MM[‘Age’].mean()
Output: 63.11
data_MM['Age'].median()
Output: 63.0
As we can see that Mean >中位数,数据是右偏的,偏度为 0.04。由于偏斜度非常小,约为 0,所以分布图显示了几乎正态分布。
plt.figure(figsize=(8,7))
sns.distplot(data_MM['Age']);
plt.title("Distribution plot for Age:");

图 5。说明了“年龄”的分布图。图片由作者使用 Jupyter 笔记本制作。
通常,整个数据的分布图可能无法描绘正确的画面。因为 sns.displot()不允许我们按类别分解数据,所以我们可以使用 catplot 创建一个 violin 图。注意,我们不能使用 pyplot 和 figsize 来设置画布的高度和宽度,因此我们使用 catplot 中的 height 选项。
sns.catplot(x="Age", y="Industry",
kind="violin", data=data_MM, height=12);plt.title("Distribution plot for Age:");

图 6。说明了“年龄”按“行业”的分布图。图片由作者使用 Jupyter 笔记本制作。
注意-没有必要按类别分析分布,但对于倾斜数据,将数据恢复到正态分布是很重要的,因此,我们可以只转换特定类别的数据,而不是转换整个数据。
线性模型(回归&逻辑)使用“正态数据分布”作为核心假设之一。像人工神经网络这样的深度学习模型也使用参数方程,假设数据是正态分布的。
3.极端值
均值、中值和众数是在识别异常值时广泛用于理解数据分布的主要趋势。当我们测量同类事物时,相对大量的此类测量值会聚集在中间值周围。这样的值被称为集中趋势/统计平均值的度量。
平均值计算如下:

图 7。测量平均值。作者使用 Latex 制作的图像。
中位数计算如下:

图 8。测量中间值。这个公式适用于奇数个观测值。对于偶数个观察值,中位数计算为第 N/2 次和第(N/2)次+1 次观察值的平均值。例如,如果有 10 个观察值,我们将它们按升序排列,然后取第 5 个和第 6 个观察值的平均值作为中间值——作者使用 Latex 准备的图像。
Q1 和 Q3 的四分位数计算如下:

图 9。测量四分位数。k 是四分位值,即对于 Q1,k=1,对于 Q3,k=3。作者使用 Latex 制作的图像。
例如,考虑下面的一组数字:11 个学生解决一个难题所用的时间,以秒为单位,按 7、12、13、17、18、20、22、24、25、30、45 的顺序排列。由于𝑛在这个问题陈述中是 11,𝑞将永远是 0。因为(𝑛+1)是 12,而 MOD 4 是 0,所以对于𝑘=1, p=floor(1 x (12/4)),也就是 3。对于 K=3,p =地板(3 x (12/4)),也就是 9。因此,从上面的列表中,Q1 = Xp 或 X3,13,Q3 等于 X9,25。

图 10。说明了计算 Q1 和 Q3 的方法。作者使用 Latex 制作的图像。
四分位数间距(IQR) = Q3 — Q1
箱线图使用 IQR 基础来计算数据中的异常值。IQR 可以识别较低和较高的晶须,它们被用作逼近数据中异常值的阈值。任何超出这些阈值的数据点都被视为异常值。
- 下须= Q1–1.5 倍 IQR
- 上须= Q3 + 1.5 x IQR
data_MM['Age'].describe()count 2676.000000
mean 63.113602
std 13.445153
min 18.000000
25% 54.000000
50% 63.000000
75% 73.000000
max 99.000000
根据上面的例子,Q3 = 75%或第 75 百分位= 73,Q1 = 25%或第 25 百分位= 54。IQR = 73–54 = 19。下须= 54–1.5x 19 = 25.5,上须= 73 + 1.5 x 19 = 101.5
plt.figure(figsize=(15,7))
sns.boxplot(x = ‘Age’, data = data_MM, showmeans=True)
plt.title(“Boxplot for Age”)
plt.show()

图 11。说明了年龄的箱线图。请注意,下面的晶须标记为 25.5,超出的点被视为异常值。上须的理想值应该是 101.5,但是由于没有超过该阈值的值,数据中的最高年龄 99 被认为是上须。图片由作者使用 Jupyter 笔记本制作。
plt.figure(figsize=(30,9))
sns.boxplot(y = 'Age', x="Industry", data = data_MM, showmeans=True)
plt.title("Boxplot for Age")
plt.show()

图 12。展示了按行业划分的年龄箱线图。图片由作者使用 Jupyter 笔记本制作。
根据不同模型对数据分布的假设,离群值对于模型的良好运行同样重要。一般来说,有偏差的数据表明存在异常值。使用聚类分析时,离群值往往会形成自己的聚类,从而产生不正确的分段。存在异常值时,线性模型的最佳拟合线不正确。有多种方法可以处理异常值:
- winsorization——将离群值限制在上下须上。这些须状物由 IQR 或者甚至标准差(例如±2/3 标准差)来定义

图 13。说明了方差和标准差的度量。图片由作者使用 Jupyter 笔记本和 Latex 制作。
- Tukey 的权力阶梯—对于任何有偏差的数据,如果我们在两个变量 X 和 Y 之间创建一个图(比如年龄和净值,我们需要将净值转换为数字),该图的形状可用于确定数据的转换类型。

图 14。说明了 Tukey 决定数据转换的权力阶梯。图片由作者使用 PowerPoint 制作。
data_MM['Value']=data_MM['NetWorth'].apply(lambda x: x.replace("$","").split()[0])data_MM['Value']=data_MM['Value'].astype('float')data_MM.head()plt.figure(figsize = (15,8));sns.scatterplot(x="Age", y="Value", data=data_MM);

图 15。展示了年龄和估价(价值)的散点图。随着年龄的增长,估值也在上升,因此我们可以对年龄和平方估值进行对数转换。图片由作者使用 Jupyter 笔记本制作。
皮尔逊相关系数—了解多重共线性
为了测量一个变量如何随着另一个变量的变化而变化,我们计算协方差。当我们用两个变量各自的标准差来归一化这两个变量的协方差时,我们得到了皮尔逊相关系数。协方差和皮尔逊相关系数计算如下:


图 15。说明了两个变量 X 和 Y 及其相关系数之间的协方差度量。适马指的是这些变量的标准差。作者使用 Latex 制作的图像。
- 该指标仅告诉我们两个变量一起变化的程度,而不是得出一个变量的变化导致另一个变量的变化的唯一证据。
- 正值表示两个变量朝同一方向移动,而负值表示两个变量朝相反方向移动。
我们只能确定两个变量之间关系的方向,但是为了确定关系的强度,我们计算两个变量的相关系数。
correlation = data_MM.corr().round(2) # creating a 2-D Matrix with correlation plotscorrelationplt.figure(figsize = (15,8)) #figsize=(width,height)sns.heatmap(correlation, annot=True, cmap='vlag_r', vmin=-1, vmax=1); #color palette

图 16。展示了数据中所有数值变量的关联矩阵(热图)。图片由作者使用 Jupyter 笔记本制作。
与协方差不同,相关系数与比例无关,可以取-1 到+1 之间的值。接近-1 的值表示变量之间的负相关。例如,等级和值之间的相关性等于-0.48。这表明随着值的增加,等级降低(由于等级 1 被认为是最高的,所以等级会更高)。
多重共线性在创建驱动模型时尤其重要。如果两个或多个变量高度相关,那么线性模型的参数估计可能不是精确的表示。通常,VIF(方差膨胀因子)用于识别多重共线性,但是相关性可以在构建模型之前筛选此类变量。
参考
- 卡格尔。(未注明)。2021 年福布斯亿万富翁。2022 年 1 月 20 日检索,来自 kaggle.com 网站:https://www . ka ggle . com/roysouravcu/Forbes-billionaires-of-2021?select=Billionaire.csv
关于作者:高级分析专家和管理顾问,帮助公司通过商业、技术和组织数据的数学组合找到各种问题的解决方案。一个数据科学爱好者,在这里分享、学习、贡献;你可以和我在 上联系 和 上推特;
编程中的数据类型和类型系统介绍
了解 Python 中的一般类型系统

阿列克斯·多罗霍维奇在 Unsplash 上的照片
在编程和数据库中,数据类型用于指定变量的类型。这也决定了,例如,哪些操作可以用这些变量来执行,哪些会导致错误。例如,当存储文本时,诸如加法之类的数学运算是不可能的。
什么是数据类型?
在计算机科学中,人们定义数据类型,对于这些数据类型,某一组操作是可能的,不会有问题。对于属于一种数据类型的所有数据,可以完成这些操作,并确保不会出现错误消息。
例如,对于“整数”数据类型,定义了“加”和“减”操作。这意味着,如果我们有“整数”数据类型的任何两个元素,那么它们可以相加或相减,不会发生错误。另一方面,“文本”数据类型的两个对象不能执行此操作,因为这不是为该数据类型定义的。
数据类型的使用使得在编程中在几个变量之间执行某些操作成为可能。对于每种数据类型,都定义了特定的计算或转换,可以使用相同数据类型的另一个变量执行这些计算或转换,而不会出现问题。
为了确保在使用多个变量的操作中不会出现运行时错误,我们使用了所谓的类型系统。
计算机科学中的类型系统是什么?
类型系统是计算机科学中使用的术语,用来描述在编程语言中限制变量取值范围的可能性。具有类型系统的编程语言称为类型化的。这些系统总共可以分为三类:
- 强与弱:这是关于各自的编程语言区分类型的严格程度。例如,严格类型系统不允许变量在定义后转换为另一种数据类型。
- 动态与静态:这个维度是关于对象的类型发生的时间。例如,在 Python 中,变量本身没有数据类型,只有赋给变量的对象才有。这被称为动态类型化。但是,这也意味着直到整个程序启动后,才会检测到由不兼容的数据类型引起的错误。对于静态类型(例如在 Java 中),变量的数据类型必须在编写程序时显式定义。
- 显性与隐性:这个维度与静态/动态类型紧密交织。这是一个变量的数据类型是否已经在定义过程中明确指定,或者只能通过对象的赋值来隐式识别的问题。
根据编程语言或数据库的不同,定义了不同的数据类型,这也是名称可能略有不同的原因。然而,不同系统和编程语言之间的通用数据类型非常相似。
整数
整数用于表示数字,即表示不带小数位的正数和负数,例如,-841 或+903。
浮点(浮点)
float 数据类型也用于数值变量,但与整数不同,它们也有十进制数,例如-130.45 或+923.58923。
线
该字符串又表示文本变量,其值存储在引号中,即“.”或“”。除了由字母组成的字符串之外,字符串还可以存储数字或其他符号。然而,这些不被解释为数字,这意味着它们不可能进行算术运算。
布尔代数学体系的
当变量只能取两个可能值中的一个时,使用布尔数据类型。在许多情况下,要么使用值对 0/1,要么使用真/假。
Datetime 数据类型存储包含日期和时间的值,如 2021–09–12 15:23:41,因此用于此的格式为 YYYY-MM-DD hh:mm:ss。
时间戳
时间戳是在变量中存储时间信息的另一种方式。最常见的是所谓的 Unix 时间戳,它测量自 1970 年 1 月 1 日以来过去了多少秒(取决于格式,也可以是毫秒)。
性格;角色;字母
这种数据类型用于存储单个字母、符号、数字等..也可以存储单个空格字符。
作为一名程序员,你为什么应该关心类型系统?
在许多编程语言中,数据类型是显式的,所以当定义一个变量时,必须指定它的数据类型。所以在这些情况下,程序员别无选择,只能处理数据类型。
然而,在隐式编程语言(如 Python)中,至少记住数据类型也是有意义的。一旦 Python 识别出所需操作不可执行的数据类型,就会返回所谓的类型错误。
因此,当这些错误发生时,您立即知道去哪里重新运行代码。对于给定的数据类型,它必须是不可能的操作。因此,要么我们对给定的数据类型使用了错误的操作,要么变量具有我们不希望它们具有的其他数据类型。
为了避免这种错误,我们可以检查变量的数据类型,并且只有在我们确定类型正确的情况下才运行操作:
从 Python 版本 3 开始,在定义函数时也可以声明数据类型。这样,程序员就准确地定义了她期望哪些类型作为输入,哪些类型作为输出。
但是,如果违反了此声明,不会自动发出类型错误。然而,该规范帮助团队中的其他程序员更好地理解代码,并根据手头的功能调整下游功能。此外,它有助于编程前端更好地将自动完成功能调整到给定的数据类型,从而在早期阶段检测到错误。
数据类型的另一个优势是性能和数据存储的优化。例如,整数变量比十进制数需要更少的内存。同时,整数处理值的速度也比 double 类型的变量快。
这是你应该带走的东西
- 数据类型用于定义变量的类型。这也决定了哪些操作可以使用变量,哪些不可以。
- 类型化是限制变量取值范围的能力。
- 通常,数据类型有整数、浮点、字符串、布尔、日期时间时间戳和字符。此外,还定义了更多的数据类型,这些数据类型会因编程语言或数据库而异。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**
* *
数据版本控制简介
原文:https://towardsdatascience.com/introduction-to-data-version-control-5c4a83276b9
PYTHON |数据|编程
数据版本控制简介
使用 Hangar 在 Python 中实现自己的 DVC 的分步指南

雅罗斯拉夫·穆齐琴科在 Unsplash 上拍摄的照片
什么是数据版本控制(DVC)?
任何生产级系统都需要某种版本控制。当前真相的唯一来源。任何持续更新的资源,尤其是由多个用户同时更新的资源,都需要某种审计线索来跟踪所有的更改。
在软件工程中,对此的解决方案是 Git 。如果你在生活中写过代码,那么你可能对 Git 的美妙之处很熟悉。Git 允许我们提交变更,从一个源创建不同的分支,并将我们的分支合并回原始分支,等等。
除了数据集,DVC 完全是同样的范例。看,当不同的用户在相同的数据集上进行不同的实验时,实时数据系统不断地接收更新的数据点。这就导致了同一个数据集的多个版本,绝对不是单一的真实来源。
此外,在机器学习环境中,我们还会在同一数据集的不同版本上训练同一“模型”的多个版本(例如,模型重新训练以包括较新的数据点)。如果没有适当的审核和版本控制,这将会创建一个数据集和实验的复杂网络。我们绝对不希望这样!
因此,DVC 是一个通过记录特定数据集的变化来跟踪数据集的系统。有多种免费和付费的 DVC 解决方案。我最近发现了机库,一个完全开源的 Python DVC 包。让我们看看它能做什么,好吗?
使用机库
hangar 包是一个纯 Python 实现,可以通过 pip 获得。它的核心功能也与 git 紧密相关,这对学习曲线有很大帮助。我们也可以选择通过命令行或使用其专用的 Python 客户端与 hangar 交互。
一些可用的功能包括:
- 检出 —切换到新的分支或参考点
- 提交 —将当前变更添加到当前分支
- 分支 —创建一个指向特定提交的引用点
- 合并——合并一个分支到另一个分支的变更
- diff —比较两个分支之间的变化
- 推送 —上传本地版本到远程仓库
- 拉/取 —从远程存储库更新数据集的本地版本
- 日志 —显示提交历史
注意:远程存储库是当前事实的唯一来源。
这里要注意的积极的一点是,Hangar 不是构建在 git 之上的,而是模拟了 git 的功能。这使得它更快。
我们可以使用以下方式通过 pip 安装 hangar:
pip install hangar
安装好 Hangar 之后,我们就可以直接把包导入 Python 了。
我们需要做的第一件事就是创建一个数据仓库。我们可以从 Hangar 包中导入 Repository 类,并用它来定义我们的存储库。
如果这是我们第一次使用一个特定的存储库,我们还必须使用init()函数初始化它。
在我们继续我们的数据版本化例子之前,让我们首先讨论 Hangar 背后的方法论。
接近机库
Hangar 背后的主要学习曲线是理解与软件包交互的最佳方式。机库包括四个主要部分:
仓库
我们可以将存储库视为我们的项目仓库。存储库本质上是所执行的提交的集合和历史。
理想情况下,每个项目都有自己的存储库。例如,如果我们有两个主要任务—预测手写数字和预测欺诈—我们也分别创建两个存储库。
数据集
这个很简单。数据集就是,你猜对了,我们的数据集。但是数据集到底是什么呢?我们拿泰坦尼克号数据集来类比一下。数据集由什么组成?
是个别样品吗?是被监控的变量吗?这就是我们可以发挥创造力的地方。Hangar 将数据集描述为列的集合。我们接下来会讨论这个问题。
专栏
该列可以是我们喜欢的任何数据属性或特性。它可以是要素数组、标签数组、要素名称数组,甚至是唯一标识符数组。但是,列数组中的每一项都应该对应于数据集中的一个样本。目前,支持的列类型有:
- 添加字节列—字节
- add_ndarray_column —数组
- add_str_column —字符串
例如,如果我们有一个 28x28 图像的数据集,我们将选择一个数组列(每个样本的形状都是 28x28)来表示实际的数字数据。我们可以用字节或字符串列来存储它的标签,用字符串列来存储图像文件名。

作者图片
当然,以上只是如何构建数据集的指南。你正在处理的数据类型,以及要做的实验类型,都会影响到机库的构建策略。例如,也可以选择一个专用的列用于训练数据,另一个用于验证数据。
列应该是数据样本的集合。我们从一个空集合开始,随着样本的增加,集合索引增加。
数据
最后是数据。一旦我们确定了哪些列,相应地处理数据就变成了一个相对简单的任务。数据本身只是数字。它没有任何直接意义,也没有任何结构。
这样一来,让我们继续我们例子的其余部分。
假设我们有一个表格分类数据集— df —我们将简单地将整个数据集以字节的形式存储在一列中。
我们首先创建一个 WriterCheckout。WriterCheckout 对象允许我们启用一个特定的分支(在我们的例子中,我们只有一个分支:主分支)的写访问权限(也就是说,能够向活动分支写入和提交更改)。我们使用master = repo.checkout(write=True)来实现这一点。
然后,我们可以通过调用add_bytes_column命令 Hangar 创建一个名为“数据的字节列。因为这是我们第一次提交,所以我们的列仍然是空的。对于第一次提交,我们可以在索引 0 处提交数据。因为我们将列指定为 bytes 对象,所以我们必须首先将数据转换为 bytes 对象。我们最终可以调用commit函数来提交并保存我们的更改。下面,我们展示一个我们刚刚讨论过的代码示例。
注意:为了避免冲突,Hangar 不允许一个以上的 WriterCheckouts 循环使用。因此,当不使用 WriterCheckout 时,一定要关闭它。如果一个写锁已经在循环中,我们只能以只读模式签出。
如果我们想在同一个列中添加另一个提交,我们遵循相同的过程,但是改为提交到master['data'][1],对于将来的提交以此类推。每个提交还将绑定一个散列键。
机库中的分支
当我们想要在特定点获得数据的副本以在其上运行定制实验而不实际改变它时,分支变得特别有用。我们可以分支,在我们确认我们的处理是正确的之后,我们也有能力合并回主流。典型的分支流程如下所示:
Create Branch -> Checkout Branch -> Make Changes -> Commit -> Merge
我们可以使用repo.create_branch(name='test')创建分支,合并如下:
master.merge(message='message for merge', dev_branch='test')
Hangar 中的每个提交都有一个哈希键。我们可以使用该散列来精确定位分支点:
test_branch2 = repo.create_branch(name='test2', base_commit=<SOME_HASH_KEY>)
通过调用repo.log(),我们可以获得当前分支及其最新提交的日志摘要。日志示例如下所示:
* a=cf94cf8b4c5758c885c6b84d58c4fbe22f379510 (test2): added new test branch
* a=a8fe61916764b873f13c80a14ce4fda610b74df9 (test) (master): Base Dataset
我们可以得到分支之间的差异和冲突如下:
repo.diff('master', 'test2')
结束语
在这篇文章中,我们介绍了作为 Python 中 DVC 开源解决方案的机库包。这就是机库提供的所有东西吗?肯定不是!我们介绍了基础知识,并发现了如何开始使用 Hangar。和往常一样,我强烈建议你仔细阅读他们的文档,并使用你自己的用例进行实践。
你喜欢这篇文章吗?如果是的话,也许你可以考虑成为会员来支持我和你其他喜欢的作家。
https://david-farrugia.medium.com/membership
想给我买杯咖啡吗?
https://paypal.me/itsdavidfarrugia?country.x=MT&locale.x=en_US
想联系吗?
我很想听听你对这个话题的想法,或者其他什么。如果你想联系我,请给我发电子邮件至 davidfarrugia53@gmail.com。
Linkedin — Twitter
用 R 中的 Keras 介绍深度学习
原文:https://towardsdatascience.com/introduction-to-deep-learning-with-keras-in-r-81bafd903a7a
循序渐进的教程

从意大利特伦蒂诺-上阿迪杰的圣维吉尔里奥山看出去。图片作者。
目录
1.介绍
对于数据科学来说,R 和 Python 都是有用和流行的工具。然而,当涉及到深度学习时,最常见的是找到 Python 的教程和指南,而不是 r。
这篇文章提供了一个简单的 R 语言深度学习的例子。它的目的是分享一个实用的介绍给 R 从业者,使用 Keras。
2.环境设置
在这个例子中,我们分享了可以很容易地复制并粘贴到 Google Colab 上的代码片段。
Colab 允许任何人通过浏览器用 Python 或 R 编写代码来创建笔记本,完全免费。
我们可以通过这个链接在 Colab 中创建新的 R 笔记本。从那里,我们安装 Keras 如下:
install.packages("keras")
尽管为了简单起见,我们使用了 Colab,但是本地安装过程也同样简单。我们现在可以导入所需的库:
library(tidyverse)
library(keras)
注 : install.packages("keras")也安装 TensorFlow。可以从已安装的软件包列表中查看 Keras 和 TensorFlow 的可用版本:

图片作者。
3.资料组
我们的目的是对手写数字图像进行分类。对于这个例子,我们使用了机器学习社区的经典 MNIST⁴数据集。
数据集具有以下特征:
- 60,000 幅训练图像和 10,000 幅测试图像。
- 尺寸为 28 x 28 像素的图像。
- 10 个类别(数字从 0 到 9)。
- 灰度图像:像素值介于 0(黑色)和 255(白色)之间。
神经网络需要张量形式的数据。张量是具有任意维数(D)的代数对象。例如,我们可以把向量看作 1D 张量,把矩阵看作 2D 张量。
在图像的情况下,我们需要一个向量空间来表达:
- 图像数量(N)
- 图像高度(H)
- 图像宽度(W)
- 颜色通道(C),也称为颜色深度。
因此,在深度学习任务中,图像通常被表示为具有形状的 4D 张量:N x H x W x C
在灰度图像的情况下,每个样本的颜色通道是一个单一的数字(从 0 到 255)。因此,可以省略通道轴或使其等于 1。
让我们从 Keras(在 Apache 2.0 License⁵下)导入 MNIST 数据集,并验证训练和测试图像的形状:

作为 3D 张量的训练和测试图像(X)。颜色通道轴被省略(灰度)。图片作者。
将输入观测值表示为张量适用于任何数据类型。例如,具有 300 行(样本)和 8 列(特征)的 csv 文件形式的表格数据可以被视为形状为 300 x 8 的 2D 张量。
我们可以通过相应的标签来看一些样品:

对每个类别 x(从 0 到 9)调用 plot_digits_by_class(x)得到的手写数字样本。
4.预处理
由于颜色值在[0,255]区间内,我们可以将它们缩放到[0,1]区间内。此外,我们可以通过将图像从 2D 28 x 28 展平到 1D 784(28 x 28)来重塑输入,而不会丢失信息:
注意 : Keras(和其他公共库一样)希望通过在行主排序(来自 C 语言)中填充新轴来重塑数组。这就是array_reshape()的行为。 R 修炼者可能更熟悉dim<-() 处理矩阵形状。尽管如此,dim<-()在列中填充新轴-主要排序(来自 Fortran 语言):

array _ shape vs dim
The labels must be converted from a vector with integers (each integer representing a category) into a matrix with binary values and columns equal to the number of categories:

Image by author.
5. Building the neural network
5.1 Define the layers
The cornerstone of a neural network is the 图层。我们可以将该层想象成一个模块,它提取对最终目标有用的输入数据的表示。
我们可以通过顺序堆叠层来构建神经网络。Keras 允许通过利用keras_model_sequential来做到这一点。在本例中,我们创建了一个由三层组成的网络:
- 产生 512 个单位的输出空间的完全连接(或密集)层。
- 一个辍学层要在训练期间"随机地退出20%的神经元。简而言之,该技术旨在提高模型的泛化能力。
- 一个输出 10 个单位的最终密集层和一个 softmax 激活功能。该层返回每个类别的概率分数数组,每个分数是当前图像表示 0、1、2…直到 9:
我们可以按如下方式检查模型的结构:
model %>%
summary()

图片作者。
5.2 编译
在编译步骤中,我们需要定义:
- 损失函数 :
-损失函数必须提供模型误差的合理估计。
-网络试图在训练期间最小化该功能。 - 优化器 :
-它指定模型的权重在训练期间如何更新。
-它利用了损失函数的梯度。 - 指标 :
-在训练过程中要监控的一组指标。
5.3 合身
拟合模型意味着在训练期间找到一组最小化损失函数的参数。
输入数据不会作为一个整体进行处理。该模型批量迭代训练数据,每个数据的大小为batch_size。对所有训练数据的迭代被称为epoch。在拟合模型时,我们必须声明历元数。在每个时期之后,网络更新其权重以最小化损失。

培训过程中的指标。图片作者。
6.测试集性能
在训练一个估计器之后,评估它在样本外数据上的表现是一个好的实践。我们可以在测试集上测量准确度(正确分类的手写数字的比例):

图片作者。
让我们看看一些分类错误的数字:

分类错误的数字。图片作者。
7.结论
在这篇文章中,我们在 Keras 中创建了一个简单的神经网络,同时分享了对深度学习概念的介绍。特别是,我们使用了 R 语言,它通常不像 Python 那样在深度学习教程或指南中常见。
值得注意的是,两个最流行的深度学习框架 Torch 和 TensorFlow 也支持 R。
您可以在此找到更多信息和示例:
- ⁶的张量流
- ⁷的火炬
- Francois Chollet,J.J. Allaire,“深度学习与 R ”,曼宁,2018⁸.
8.参考
1https://colab.research.google.com
[2]https://colab.research.google.com/notebook#create=true&语言=r
https://tensorflow.rstudio.com/install/
https://en.wikipedia.org/wiki/MNIST_database
[5]https://github.com/keras-team/keras/blob/master/LICENSE
[6]https://tensorflow.rstudio.com/
https://www.manning.com/books/deep-learning-with-r
嵌入、聚类和相似性介绍
原文:https://towardsdatascience.com/introduction-to-embedding-clustering-and-similarity-11dd80b00061
对潜在空间的广泛介绍
近距离观察 ML 和潜在空间的关键元素

照片由贾维尔·阿莱格·巴罗斯在 Unsplash 上拍摄
在这篇文章中,我们将对嵌入、相似性和聚类做一个总体介绍,这些是大多数 ML 的基础,也是理解潜在空间的关键。在计算机中将现实世界表示为数据的过程被称为嵌入,在现实世界可以被分析并用于应用程序之前,这是必要的。相似性发现真实世界中的嵌入彼此之间有多相似,并支持产品推荐等应用。聚类识别真实世界嵌入中的组,并实现诸如识别哪些书是关于相同主题的应用。
目录:
- 1.1 嵌入
- 1.2 相似性
- 1.3 集群
- 1.4 K 均值聚类
- 1.5 结论
1.1 嵌入
计算机可以表现和分析现实世界发生的事情。示例可能包括表示一本书并在以后阅读时使用的文本,或者表示可以在线与其他人共享的绘图的图像。
在计算机中表示某物的过程叫做嵌入。计算机只知道数字,因此必须如此表示嵌入。嵌入从询问我们希望表达什么以及如何表达的问题开始。一本书可以是一个例子,通过用数码相机拍摄其页面的图像来询问关于其颜色的问题。这种相机有许多微小的传感器,每个传感器都会询问它看到什么颜色,并将该颜色转换成数字。这些传感器将相机看到的东西分成小方块,每个方块是一个传感器和颜色——传感器越多,分辨率越高。

图 1.1 —将图像转化为数字的过程。由杰斯·贝利在 Unsplash 上拍摄的图书照片
嵌入一本书需要很多数字。一个“G”需要 5x5=25 个数字,一个 48 个字符的句子需要 25x48=1.200,一页 28 行需要 1.20028=33.600,一整本 250 页的书需要 33.600250=8.400.000 个数字——这是一个很大的数字!
拍照时,我们要求的不仅仅是书的内容;我们还要求看看它看起来怎么样,书的状态,照明条件,以及它离相机有多远。我们不经常需要关于一本书的这么多细节;好的一面是,我们可以用其中一些来换取更少的数字。我们可以询问书的内容。数字和字母通常采用一种称为 ASCII 的格式,其中数字介于 48 和 122 之间,计算机知道 48 是“0”,71 是大写的“g”。使用 ASCII 意味着一个字符是 1 个数字,一个句子是 1x48=48 个数字,一页是 4828=1344 个数字,一本书是 1433250=336.000 个数字。

图 1.2 —一本书上的一个句子转换成了 ASCII 码。由杰斯·贝利在 Unsplash 上拍摄的图书照片

图 1.3 —计算机通常如何存储字符(数字和字母)的 ASCII 表
我们可以用更少的数字来表示这本书。也许这本书的内容并不重要,重要的是它的内容。一本书的内容可以用幻想、传记、恐怖和非小说等体裁来表现。让我们把流派的数量提高到一个很高的数量,比如说有 256 种不同的流派。如果是给定的流派,我们可以用 1(真)来表示这本书,如果不是,用 0(假)来表示。这本书现在可以只用 256 位或 32 个数字(每个数字 8 位)来表示,无论大小!这是嵌入信息和大小之间的权衡。

图 1.4 —用流派表示的书。1(真)表示这本书是给定的流派,0(假)表示不是。
不同的应用需要不同的嵌入。推荐新书时更容易使用流派,因为我们可以寻找与我们喜欢的书具有相同流派的书籍。但是我们需要阅读书籍的内容和首页的图像来查看它的外观。
一个好的嵌入要求我们知道应用程序需要什么信息,以及将信息转换成数字的方法。就像书的例子一样,应用程序的不同部分需要不同的表示。
1.2 相似性和矢量化
相似性的目标是找到相似的嵌入。一个例子是使用相似性向用户推荐与他们已经阅读过的书籍相似的书籍。
但是什么是相似呢?相似性基于嵌入(也称为测量、样本或点),这些嵌入可以绘制到一个坐标系中,也称为维度空间(或简称为空间)。当把一个嵌入放到一个坐标系中时,我们称它为一个点。下面是一个将四本书设定到 2D 坐标系的例子。

图 1.5——四本书被放置在一个 2D 坐标系中,这个坐标系基于它们所包含的小说和生活经历
如果点在空间上靠得更近,它们就更相似。书籍也是如此;两本都是非虚构的书(A 点和 B 点)比一本是虚构的和一本不是虚构的书(A 点和 C 点)更相似。相似性是两点之间的距离。图 1.6 显示 L1 < L2 which means that A and B are more similar than A and C.

Figure 1.6 — Distance between A and B (i.e. L1) and between A and C (i.e. L2). A and B are closer than A and C as L1 < L2.
In practice, vectorizing the points allow us to calculate the distance (i.e., similarity) between them. Vectorization requires only points originating from (0,0). E.g., points (2,3) = vector [2, 3].

Figure 1.7 — Points can be converted to vectors by assuming they start in (0, 0)
The distance is the length of the vector traveling between the two points, called the Euclidean distance. Finding the vector between two points only requires subtraction of their vectors.

Figure 1.8 — Euclidean similarity is the distance between two points

Equation 1.1 — Formula for calculating the euclidean similarity score
In Python, the euclidean similarity is calculated by creating two vectors with Numpy ,减去它们,取范数。
代码 1.1 —使用等式 1.1 计算两本书之间的欧几里得相似性
确定相似性的另一种方法是余弦相似性,它着眼于向量之间的角度,而不是它们端点之间的距离。这个想法是,概念/特性之间的比率比它们对这些特性的优先程度更重要。A 点更侧重于小说,B 点侧重于人生旅程——即使它们在欧几里得空间中彼此接近,它们的概念/特征也有很大不同。

图 1.9-余弦相似度是两个向量之间的角度

等式 1.2-计算余弦相似性得分的公式
在 Python 中,余弦相似性是通过用 Numpy 创建两个向量,并使用 Scipy 找到两者之间的余弦来计算的。
代码 1.2 —使用等式 1.2 计算两本书之间的余弦相似度
我们可以使用欧几里德相似度和余弦相似度;它们执行不同的行为,设计者的任务是找出最适合应用的行为。
1.3 集群
聚类是一种帮助我们在数据中找到组的技术。一个例子可能是根据我们喜欢阅读的群体/流派推荐新书。直观上,一个组由相似的对象组成,它的对象越近,它就变得越简洁。任务是确定哪些点最相似,以及如何将它们组合成适当数量的组。聚类方法使用相似性公式来评估点之间的相似性;在下图中,我们通过使用点之间的欧几里得相似性并组合最接近的点来创建两个组。对这两个群体的分析表明,我们的初级阅读包括学校书籍和幻想(没有迹象表明我们是否喜欢阅读学校书籍或只是因为我们需要而阅读它们)。

图 1.10-通过寻找密切相关的点对,可以创建两个组。
组数是一个超参数,也就是说事先定义组数的不是计算机,而是人。选择适当数量的集群(即组)至关重要。但是如何选择适当数量的集群呢?图 1.11 分析了一个、两个和四个聚类的数据,以显示变量的影响。

图 1.11 —向数据中添加一个、两个和四个聚类所提供的信息。一个和四个聚类只提供我们已经拥有的信息,而两个聚类帮助我们了解用户阅读什么样的书籍。
选择一个聚类告诉我们,我们喜欢书,这可能不是很有帮助,因为我们正在制作一个关于分析书籍偏好的应用程序。四个聚类显示我们喜欢书 A、B、C 和 D,但在同一组中对推荐新书贡献很小。两个集群更适合这个场景,因为它们提供了学校和幻想书籍是我们阅读最多的信息。对于所有应用来说,没有完美的集群数量——如果我们读了更多类似下面例子的书会怎么样?两个或四个小组合适吗?

图 1.12 —上例中的四个集群太多,无法提取有用的信息。在本例中,需要四个集群来正确理解数据。没有完美的集群数量,都是特定于应用程序的。
常用的两种聚类方法是 k-means 聚类 和 层次聚类 。在 K-均值聚类中,定义了“K”个聚类,并在数据中找到它们,就像上面的例子一样。在分层聚类中,我们定义了一个阈值,并使用它通过确定聚类的不同程度来确定聚类的数量。层次聚类通常是通过将最接近的点组合成越来越大的聚类(自下而上)或通过建立单个聚类并将其拆分直到它们足够明显(自上而下)来实现的。

图 1.13-(左)坐标系统中绘制的点。(右)最接近的点被组合成一个聚类,创建一个可用于未来组合的新点。该过程继续,直到组之间的距离超过预定阈值。
使用集群有两种主要方式。第一是看现有数据中存在哪些组,第二是找新点属于哪个组。我们可以使用第一种方法来查找书籍组,并用流派标记每一组。之后,我们可以拿一本新书,用第二种方法,通过看它最接近哪一组来分类这本书属于哪一个流派。确定新点的组是分类任务的基础,其中我们使用现有数据来估计类/组。

图 1.14-新点属于哪个组取决于它最接近该点的组
这篇博文的剩余部分更详细地解释了 K-means 聚类,并提供了一个实际的实现。在此处更详细地解释了分层聚类。
1.4 K 均值聚类
K-均值聚类中的“K”定义了聚类的数量。K=2 意味着必须在数据中找到两个聚类,K=4 意味着必须在数据中找到四个聚类。“均值”定义了每个聚类如何成为其组的均值,将其置于其组的中心。
一个聚类通过在相同的空间中放置一个新点来表示一部分数据。新点现在可以表示该部分数据。

图 1.15 —将聚类作为代表部分数据的新数据点放入空间
K-means 的目标是为每个聚类找到最佳位置。放置一个集群并不像上面那样简单,每个集群都在一个彩色圆圈的中心。现实中没有这样的圆圈,只有如下所示的点。代码 1.3 用 Python 定义了图 1.16 中的点,并在示例中用于说明如何实现 K-means。

图 1.16-组不是用颜色或圆圈表示的。它更像是放置在坐标系中的无色点(对人类而言)或表格中的点(对计算机而言)。
代码 1.3-从图 1.16 的表格中定义数据点
寻找 K-聚类的第一步是在空间中随机放置 K-点。使用随机点可能看起来违反直觉,因为为什么不将它们均匀地分布在空间中呢?主要原因是所有数据看起来都不一样,这通常会导致等距点不在实际组的中心。如果一切顺利,我们希望在数据中找到真实的组。k-均值聚类不能确保我们找到最佳的可能聚类,只能确保我们找到接近我们的点的最佳聚类。随机点不能确保良好的表示,但是如果多次进行,在某个点随机放置的点很有可能会产生良好的结果。使用等间距的点将每次给出相同的聚类,或者如果系统地搜索,计算量太大。

图 1.17-聚类的不同开始位置会产生不同的结果
所以,我们在空间中随机放置点。没有理由将它们放在现有数据点的最小值和最大值之外,所以在两者之间。

图 1.18-随机生成的聚类点是在现有点的范围内生成的
代码 1.4 —为 n 簇生成随机起始位置。图 1.18 中的原理用于在数据点定义的范围内产生点。
下一步是在集群之间分发每个原始数据点。一个数据点属于最能代表它的聚类。最佳表示是与数据点最相似的聚类,并使用相似性试探法进行测量。在这个例子中,我们将使用欧几里得。

图 1.19-通过计算每个点与每个点的距离并选择最近的一个点来确定每个点与哪个聚类最相似。欧几里德距离被用作启发法。
代码 1.5-计算每个数据点最接近的聚类。
分发数据点之后的下一步是 K-means 聚类的“均值”部分。“平均值”指的是一个聚类应该是它所代表的数据点的平均值。聚类可能不是分布后的平均值,因为它们仅代表最接近的数据点。因此,有必要移动每个聚类,将其位置更改为其数据点的平均值。

图 1.20 —每个聚类都被更新为最接近它的数据点的“平均值”。

等式 1.3-计算聚类 j 平均值的公式,聚类 j 中的点距离聚类 j 最近。
代码 1.5-计算每个聚类的新位置,作为最接近它的数据点的平均值。等式 1.3 用于计算单个聚类的平均值。
一个聚类在它的新位置上可能更靠近其他数据点。需要再次计算分布,以确保每个聚类代表正确的数据点。

图 1.21-重新计算哪个点属于哪个聚类,并将聚类移动到它们的新均值。
代码 1.5 —所用函数的完整实现可以在 git-repository 中找到
k 均值聚类是一个迭代过程。这意味着我们无法在一次计算中找到每个集群的最佳位置。需要计算多次,每次都越来越接近正确的地方。这个迭代过程继续进行,直到集群不再移动或者低于阈值。阈值确保该过程不会无限期地继续下去。
通过改变启发式算法(例如,余弦相似性而不是欧几里德相似性)或者改变聚类如何表示其数据点组(例如, medoids 而不是均值),很容易产生新的聚类算法。
为了让事情变得简单, SKLearn 发布了一个健壮的 K 均值聚类实现:
代码 1.6 — Sk-learn 有自己的 K 均值聚类实现,比第 1 章介绍的简单方法更好更快。
1.5 结论
我们现在已经学完了第一章,以及对嵌入、相似性和聚类的介绍。
第一部分是关于嵌入并解释了如何在计算机中表现真实世界,以及为什么问正确的问题对应用程序至关重要。一些应用程序甚至需要同一现实世界事件的多种表示(例如,书)。任务是确定哪种表示为应用程序提供最佳格式。
第二部分是关于相似性,展示了如何通过使用两个现实世界事件之间的距离来计算它们之间的相似性。存在计算相似性(例如,欧几里德距离和余弦相似性)的不同方式,每种方式导致不同的行为。选择一个为应用程序提供正确行为的方法是至关重要的。相似性的一个用例是推荐与用户喜欢的书相似的书。
最后一节是关于聚类的,展示了相似的点如何经常组合在一起,以及这些组代表了底层结构。书籍形成自然的流派或主题组(例如,科幻和传记)。找到正确的组数至关重要;太少或太多的组将为应用程序提供误导性的知识。
第 2 章 着眼于更复杂的应用,其中相似性和聚类确定图像正在搬运的动物。第二章建立了一种方法来衡量 ML 的表现,并发现第一章的方法对图像嵌入无效。第 3 章介绍了第 2 章中发现的弱点和优点,并探讨了改进 ML 的方法。
参考
1乔希·斯塔默, StatQuest: K 均值聚类 (2018),Youtube.com
[2]亚历山大·伊赫勒,聚类(2):层次凝聚聚类 (2015),Youtube.com
所有图片和代码,除非另有说明,均为作者所有。
感谢你阅读这本关于潜在空间的书!当分享我们的想法时,我们学得最好,所以请分享一个评论,无论是一个问题,新的见解,还是一个分歧。任何建议和改进都非常感谢!
如果你喜欢这本书,并且对机器学习和数据科学的新见解感兴趣,请注册中级会员,以便完全访问我的内容。关注我,以便在我发布新章节或帖子时收到电子邮件。
https://medium.com/@mathiasgronne/membership
书籍章节
简介:进入图像嵌入和潜在空间
第 1 章:嵌入、聚类和相似性介绍
第 2 章:图像嵌入和精度介绍
机器学习中的集成方法介绍
原文:https://towardsdatascience.com/introduction-to-ensemble-methods-in-machine-learning-e72c6b9ff4bc
本文介绍了机器学习中使用的主要集成方法:投票、打包、提升和堆叠

在机器学习中,偏差是模型的预测值和地面真实值之间的差异,而方差是训练集中对小波动的敏感性产生的误差。
具有高偏差可能导致算法错过目标输出和特征之间的相关关系(欠拟合),而高方差可能由对训练数据中的随机噪声建模的算法产生(过拟合)。
偏差-方差权衡是一个模型的属性,它表示可以以增加样本间估计参数的方差为代价来减少估计参数的偏差。
解决偏差-方差权衡的一种方法是使用混合模型和集成学习。一般来说,主要的集成方法有投票、堆叠、 打包和助推。
本文涵盖了这四种集成方法,介绍了每种方法的主要优缺点及其在 Python 中的实现。
在继续阅读本文之前,如果您不熟悉偏差和方差术语,我推荐您看一下文章模型评估的偏差-方差分解。
1.集成方法
1.1.投票
投票集成方法是一种机器学习模型,它将来自不同模型的预测结合起来,以输出最终预测。对于这种集成方法,模型应该是不同的,因为它使用所有的训练数据来训练模型。

图一。投票集成方法的训练模型。参考号:图片作者。
对于回归任务,输出是模型预测的平均值。相反,对于分类任务,有两种估计最终输出的方法:硬投票和软投票。前者包括从模型的所有预测中提取模式(图 2)。后者在平均所有模型的概率后使用最高的概率(图 3)。

图二。硬投票。参考号:图片由作者提供。

图 3 。软投票。参考:图片由作者提供。
投票背后的主要思想是能够通过分别补偿每个模型的误差来更好地概括,特别是当模型在预测性建模任务中预测良好时。
这种集成方法对于随机机器学习模型(如神经网络)特别有用,因为它们每次运行都会产生不同的模型。如果观察到相同的模型在使用不同的超参数时表现良好,则当组合相同机器学习算法的多个拟合时,这也是有用的。
1.2.堆垛
堆叠集成模型是投票集成模型的扩展,因为它们使用贡献模型的加权投票来避免所有模型对预测的贡献相等。
堆叠模型的架构包括基础模型(根据训练数据拟合的模型)和元模型(学习如何组合基础模型预测的模型)。常见的做法是对回归任务使用线性回归模型,对分类任务使用逻辑回归模型。
元模型是根据基本模型基于样本外数据所做的预测来训练的。换句话说,(1)不用于训练基本模型的数据被馈送到基本模型,(2)从这些基本模型进行预测,以及(3)提供这些预测和基本事实标签以适合元模型。
元模型的输入取决于任务。对于回归任务,输入是预测值。对于二元分类任务,输入通常是正类的预测值。最后,对于多类分类,它通常是所有类的一组预测值。
文献中常用的一类叠加模型叫做混合。虽然堆叠模型是根据 k 倍交叉验证期间所做的折外预测来训练的,但是混合模型是根据维持数据集所做的预测来训练的。

图 4 。堆积系综方法的训练基模型。 Ref :图片作者。

图 5 。堆叠集成方法的训练元模型。参考:图片由作者提供。

图 6 。堆积系综方法的新数据。参考:图片由作者提供。
堆叠旨在提高建模性能,但并不保证在所有情况下都能提高性能。如果任何基础模型的性能类似于叠加系综方法,这应该是更好的使用,因为它更容易解释和维护。
1.3.制袋材料
Bagging 是对训练数据进行子采样以提高单一类型分类器的泛化性能的过程。这种技术对于倾向于过度拟合数据集的模型是有效的。
用于子采样数据的方法是自举(名称装袋来源于自举 + 合计)。这种方法包括对数据进行随机采样和替换,这意味着训练数据的子集将重叠,因为我们不是分割数据,而是对其进行重采样。
一旦获得每个模型的输出,每个数据的最终预测可以通过进行回归投票,其中预测是贡献模型的平均值,或者分类投票,,其中预测是贡献模型的多数票。
实施该方法时需要考虑的一些重要注意事项是:( 1)分类器的超参数不会随着子样本的不同而变化,( 2)改进通常并不显著,( 3)它的成本很高,因为它可能会将计算成本增加 5 或 10 倍,( 4)这是一种偏差减少技术,因此当方差受限时,它没有帮助。

图 7 。bagging 集成方法的训练模型。参考:图片由作者提供。

图 8 。bagging 系综方法的新数据。参考:图片由作者提供。
这种集成方法在过拟合数据时特别有用,但在欠拟合数据时则不然,因为它通过更好地概化来减少方差。
1.4.助推
Boosting 是一种集成方法,通过串联使用弱学习器来建立模型。主要思想是每个连续模型校正其先前模型的误差。
尽管有几种类型的提升算法,如梯度提升或 XGBoosting,但第一个为二进制分类开发的提升算法是 AdaBoost。图 9 以简化的方式显示了 AdaBoosting 的工作方式,其中的任务是根据 x 和 y 特征将圆和正方形分类。第一个模型通过生成垂直分隔线来分类数据点。但是,正如所观察到的,它错误地分类了一些圆圈的数据点。因此,第二个模型集中于通过增加错误分类的数据点的权重来分类这些错误分类的数据点。对于声明对象时定义的估计量的数量,这个过程是迭代完成的。因为更详细地解释 AdaBoost 算法本身需要一整篇文章,所以我把这个视频留给感兴趣的人。

图 9 。增强系综方法的训练方法论。参考号:图片由作者提供。
在文献中,增强已被证明实现了准确的结果,因为它们是一种容易抑制过度拟合的弹性方法。然而,它对异常值很敏感,因为每个分类器都必须修复前一个分类器中的错误,而且它也很难扩大规模,因为每个估计器都将其正确性基于前一个预测器。
2.履行
本节将介绍上述每种集成方法的实现。
这是使用 make_moons 函数随机创建的数据集,它创建了两个交错的半圆。

图十。训练和测试设备。参考号:作者图片。
为了评估每个集成方法的结果,我们运行了 SVM 分类器、决策树分类器和逻辑回归作为基准。SVM 分类器是一种机器学习算法,它基于在 N 维空间(是特征的数量 N )中找到一个超平面来对数据进行清楚的分类。决策树是树结构分类,其中内部节点表示数据集的特征,分支表示决策规则,每个叶节点是结果。逻辑回归是一种统计分析方法,根据先前的观察预测二元结果。
注 1 :我刚刚选择了这三个模型来比较不同的机器学习算法,并不是因为它们对于这项任务来说是最优的。
注 2 :对于 SVM 分类器,C 参数告诉 SVM 优化您希望避免对每个训练样本进行错误分类的程度。对于较大的 C 值,如果超平面在正确分类所有训练点方面做得更好,优化将选择一个更小边界的超平面1。因此,我选择了正则项 C 为 10000,以便模型能够适应训练数据。
- SVC:准确率 84.2%,运行时间 0.07 秒。
- 决策树:准确率 86.6%,运行时间 0.02 秒。
- 逻辑回归:准确率 83.5%,运行时间 0.02 秒。

图 11 。SVC、决策树和逻辑回归模型的准确性和计算时间。参考:图片由作者提供。
对于这些图和下面显示的图,我使用了下面的代码来可视化每个模型的分类结果。虽然显示的图是针对训练数据的,但是精度和时间都是基于测试集的。
2.1。投票
以下是使用硬投票和软投票实现投票组合时的结果:
- 硬投票:准确率 88.2%,运行时间 0.12 秒。
- 决策树:准确率 87.3%,运行时间 0.09 秒。

图 12 。硬投票和软投票模型的准确性和计算时间。参考号:作者图片。
可以观察到两个输出看起来都像图 11 中决策树的图,但是模型对训练集的概括更好,因为它们对蓝色数据点中包含的红色数据点进行了错误分类,反之亦然。对于位于图像中心的区域,观察到硬投票和软投票之间的主要区别,其中被认为是一个类别或另一个类别的区域受到给予决策树分类器的权重的影响。
关于硬表决,它不输出概率,因此需要通过clf.predict(zz)改变线路代码clf.predict_proba(zz)。
2.2。堆叠
为堆积集成方法选择的元模型是一个逻辑回归,因为我们是在一个二元分类任务中,使用 5 重交叉验证进行训练。
- 堆叠:准确率 88.4%,运行时间 0.24 秒。

图十三。叠加模型的精度和计算时间。参考:图片由作者提供。
该集成模型以增加总计算时间为代价,优于硬投票和软投票。
2.3.制袋材料
使用 100 个估计量时,bagging 集合方法的精度和时间为:
- SVC:准确率 87.7%,运行时间 0.19 秒
- 决策树:准确率 89.3%,运行时间 0.16 秒
- 逻辑回归:准确率为 84.1%,运行时间为 0.32 秒

图十四。SVC、决策树和逻辑回归 bagging 模型的准确性和计算时间。参考:图片由作者提供。
对于逻辑回归,bagging 方法允许通过组合多个线性模型来获得轻微的非线性模型。当与基准比较时,bagging 方法优于它们各自的模型,因为它们提高了准确性。具体来说,可以观察到决策树的显著改进,其中 bagging 通过对具有不同训练集的几个树进行投票来帮助减少过度拟合。
这里要提出的一个有趣的话题是装袋和随机森林的区别,因为这两种方法看起来非常相似。正如在[ 2 , 3 中提到的,主要的区别取决于特征的数量(重要提示:这里我们说的是特征的数量,而不是数据的数量),因为随机森林只选择了特征的子集。观看下面的视频了解关于这个话题的更多信息。

图 15 。随机森林和决策树装袋模型的精度和计算时间。参考:图片由作者提供。
最后,这是一个如何使用 PyTorch 和 Scikit-Learn 的 bagging 包装器在神经网络上实现 bagging 的例子。为此,有必要将网络包装在一个sk learn . base . base estimator对象中。
这里,bagging 方法也以增加计算时间为代价胜过神经网络模型。

图十五。神经网络和神经网络 bagging 模型的精度和计算时间。参考:图片由作者提供。
2.4.助推
最后,增压型号的性能如下:
- SVC:准确率 88.4%,运行时间 2.40 秒
- 决策树:准确率 87.2%,运行时间 0.02 秒
- 逻辑回归:准确率为 83.7%,运行时间为 0.15 秒
对于 boosting,有两个主要参数可能会严重影响模型的性能。首先,学习率定义了权重/系数改变的激进程度。那么,估计量的数量就是模型中使用的树的数量。学习率越低,就需要更多的树来训练我们的模型。此外,学习率越低,模型学习越慢,但它变得越健壮和有效。学习率和估计器的数量分别被设置为 0.1 和 50。

图十六。SVC、决策树和逻辑回归增强模型的准确性和计算时间。参考:图片由作者提供。
虽然 SVM 分类器的结果有所改善,但决策树的结果并没有显著改善,因为它仍然过拟合训练数据。然而,当修改决策树的最大深度时,这个结果会改变,因为它解决了决策树模型的欠拟合问题。

图十七。决策树模型的准确性和计算时间。参考号:图片由作者提供。

图十七。决策树提升模型的准确性和计算时间。 Ref :图片作者。
3.摘要
本文介绍了四种主要的集成方法(投票、堆叠、打包和提升),展示了它们的主要优点、缺点和实现。这里是结果的总结,因此您可以很容易地比较每种方法在准确性和计算成本方面的结果。
基准:
- SVM:84.2% 0.08 秒
- 决策树:86.6% 0.02 秒
- 逻辑回归:83.5%和 0.02 秒
投票:
- 硬投票:86.2% 0.12 秒
- 软投票:87.3%零 0.09 秒
堆叠:
- 88.2%和 0.24 秒
装袋:
- SVC: 87.7%和 0.19 秒
- 决策树:89.3% 0.16 秒
- 逻辑回归:84.1%和 0.32 秒
增压:
- SVC: 88.4%和 2.4 秒
- 决策树:87.2% 0.02 秒
- 逻辑回归:83.7%和 0.15 秒
如果你喜欢这个帖子,请考虑 订阅 。你将获得我所有的内容+所有其他来自牛逼创作者的文章!
参考
【1】栈交换,线性核的 SVM 中 C 的影响是什么?
【2】CSI as,装袋和随机森林有什么区别?
【3】栈交换,如果只用一个解释变量,装袋和随机森林有什么区别?
[4] YouTube,自举主要观点
[5]柯林斯 Ayuya,工程教育。
[6]培养基、集合方法:装袋、助推、堆积。
【7】CSI as,装袋和随机森林有什么区别?
【8】栈交换,如果只用一个解释变量,装袋和随机森林有什么区别?
[9]中等,叠加和混合高级集成方法的直观解释
R 中的 ggplot2 简介
原文:https://towardsdatascience.com/introduction-to-ggplot2-in-r-73242b99889
让我们了解一下 GGplot2,R 中最著名的库之一,是如何工作的

照片由 Launde Morel @unsplash.com 提供
【免责声明:此帖子包含一些我的 Udemy 课程的附属链接】
ggplot2 ,一个开源的数据可视化包,是最著名的 R 库之一。15 年前首次发布,它被认为是 R 编程语言中最灵活、最方便的可视化库之一。这是基于 R 的绘图能力的巨大升级,因为它在构建绘图时支持几个扩展,非常灵活和可调整。
如果你以前从未使用过面向代码的可视化库,那么使用 ggplot2 可能会有点困难。首先,它是以模块化的方式构建的,您可以通过使用串联添加元素来堆叠几个函数。第二,它包含了大量可以在每个模块中更改的调整和参数,这一开始可能会有点混乱。
尽管如此,我相信只要你学会了基础知识,你就能立刻构建出很多很酷的情节!在这篇文章中,我的目标是给你一个使用 ggplot2 构建 R 图的指南。我们将了解构成 ggplot2 图的主要组件,并学习如何用 R 代码调整它们。开始吧!
基地
如我所说, ggplot2 是一个“模块化”库,大多数图包含以下层:
- 映射数据和轴的绘图基础。
- 你想做的式的剧情。
- 你可能想在你的情节中包含的特定附加组件。
当我想在 ggplot2 中构建一些东西时,我总是试图将我的“心智情节”映射到这三种成分上。从第一个开始,基础说明:
- 我们要绘制的数据集是什么?
- 我们如何将变量映射到不同的轴上;
举个实际例子,让我们加载著名的iris数据集来构建我们的第一个图:
library(ggplot2)
iris_df <- iris
我将iris存储在iris_df中,只是为了让这个对象作为环境中的静态数据帧。为了构建我们的 ggplot2 库,我们可以结合使用ggplot2函数和data和mapping参数:
ggplot(
data = iris_df,
mapping = aes(x = Petal.Length, y= Petal.Width)
)
这些论点有什么用?让我们来分解一下:
data说明我们将用来填充绘图的数据集。mapping依赖于aes函数将 x 和 y 列映射到它们各自的轴上。在我们的例子中,Petal.Length将成为 x ,而Petal.Width将成为 y 。
当我们执行上面的代码时,会弹出以下窗口:

iris 数据集示例图—作者提供的图片
使用ggplot功能,我们奠定了绘图的基础,并绘制了x和y轴。**ggplot2**的一个重要部分是它只适合二维绘图——如果你需要做三维绘图,勾选https://plotly.com/r/。
在mapping参数中,我们传递的变量将代替x和y轴。请注意,R 已经将两个轴限定为数据中可用的最大值和最小值。例如,如果我们检查max(iris_df$Petal.Length),我们得到6.9,这正是我们x轴的上限!
布局好基础后,我们需要定义地块类型!我们是要做散点图、线图还是条形图?让我们使用 ggplot2 将信息传递给 R!**
绘图类型
因此, ggplot2 是一个库,我们可以在其中使用层来构建绘图。在这一陈述之后,出现了两个问题:
- 我怎样才能给情节增加一个新的层次?
- 我可以添加哪些类型的层?
我们可以添加的最重要的层之一将陈述我们想要做的情节的类型。添加一个新层非常容易——只需使用+我们就能在现有的基础上添加一个新层!**
例如,假设我们希望我们的图是散点图。为此,我们只需在图中添加一个geom_point()层:
*ggplot(
data = iris_df,
mapping = aes(x = Petal.Length, y= Petal.Width)
) + geom_point()*

ggplot2 中的散点图-作者提供的图像
通过将geom_point()添加到我们当前的图中,我们将让 R 知道我们对构建一个散点图感兴趣。使用上面的代码,我们的绘图有两层——由ggplot函数组成的基础和由geom_point()组成的类型绘图。**
但是,假设我们想做一个线图。我们要不要在剧情上再加一层?
不要!我们只是替换我们添加的模块,并添加一个geom_line()层:

ggplot2 中的线图—作者提供的图像
当然,这个数据集的结果没有太大的意义,但是,看看从散点图变成折线图是多么容易。
为了增加灵活性,我们甚至可以在新层中传递几个参数。这将调整我们绘图的一些图形方面——例如,我们点的颜色:

绿色散点图—图片由作者提供
*ggplot(
data = iris_df,
mapping = aes(x = Petal.Length, y= Petal.Width)
) + geom_point(color = 'darkgreen')*
通过在geom_point中传递color,我们让 R 知道我们的点应该有一个特定的颜色。在类型的绘图层中,我们可以调用多个参数——下面是geom_point()的摘要。**
其他层
我们的情节已经很酷了,可以被解读了。但是……我们可以做很多调整。你认为我们如何做这些小调整?当然是用新图层啦!
例如,假设我对 x 轴上的标签数量不满意。为了调整它们,我可以使用一个新的图层:T4!
*(
ggplot(
data = iris_df,
mapping = aes(x = Petal.Length, y= Petal.Width)
)
+ geom_point(color = 'darkgreen')
+ scale_x_continuous(n.breaks=10)
)*

X 轴上带有额外标签的散点图-作者图片
使用这一层将打破我的 x 轴比原来更多的桶。在这种情况下,我在新图层的n.breaks参数中选择了 10 个断点。我是否只能添加一个附加层?****
当然不是!让我们用scale_y_countinuous给y-axis添加更多的中断:
*(
ggplot(
data = iris_df,
mapping = aes(x = Petal.Length, y= Petal.Width)
)
+ geom_point(color='darkgreen')
+ scale_x_continuous(n.breaks=10)
+ scale_y_continuous(n.breaks=10)
)*

在 X 轴和 Y 轴上带有额外标签的散点图-图片由作者提供
请注意,逻辑总是相同的— 要添加一个新层,我们可以提供新的函数,并通过使用 +将它们与现有的图连接起来。
ggplot2 也是 r 中文档最丰富的库之一。该库中有数百个图层可用,您可以通过访问该库的网站快速找到关于它们的文档。
在我们结束之前,还有一件事我想告诉你。我们不需要一次构建 20 层的巨大代码块!如果它与您的用例相匹配,并且您想在进行过程中调整您的绘图,您可以将您的绘图状态保存在一个变量中,并向其中添加层——接下来让我们来看看!
分解情节
我们可以用 ggplot2 做的一件很酷的事情是,我们可以存储我们的绘图供以后使用。举个例子,我可以把我的库保存为一个变量,然后用这个变量名作为别名来添加图层:**
*example_plot <- ggplot(
data = iris_df,
mapping = aes(x = Petal.Length, y= Petal.Width)
)*
现在,我的基数保存在example_plot —调用这个变量将产生绘图的基数,正如预期的那样:

使用 example_plot 变量名的 ggplot2 base 作者提供的图像
酷的地方在于我们可以把example_plot alias 当做第一层( base )。如果我想做散点图,我可以:
*example_plot + geom_point()*

由 example_plot + geom_point()触发的散点图-图片由作者提供
如果我想换成线形图呢?让我们看看:
*example_plot + geom_line()*

由 example_plot + geom_line()触发的线图-作者提供的图像
很酷,对吧?我们的代码更加简洁,能够在变量中存储 ggplot2 层是一个很好的特性。这可以防止您用 20 多行代码生成图,而这些代码解释和调试起来可能有点麻烦。
ggplot2 被认为是 R 中数据科学的顶级库之一。学习它将会给你一个很好的工具来构建 R 编程中的情节,虽然 base R 可能足以完成几个任务,但它通常会产生过于简单、基本并且看起来不“专业”的情节。
在这篇文章中,我的目标是向你解释 ggplot2 的基础知识。接下来,检查其他图层如 geom_bar 或 geom_histogram 或试验其他可用参数。这些应该给你一个很好的概述,关于如何使用特定用例的库,以及在图的方面你可以改变什么特别的东西。
我在 Udemy 上建立了一个R 简介 和一个 学习数据科学的训练营 。这两个课程都是为初学者量身定做的,我希望你能在我身边!

绝对初学者的 R 编程课程 —图片由作者提供
***https://medium.com/membership/@ivopbernardo ***
全球预测模型简介
原文:https://towardsdatascience.com/introduction-to-global-forecasting-models-3ca8e69a6524
使用 Python 训练具有多个时间序列的预测模型

全球模型正成为时间序列预测的首选方法。以下是如何使用 Python 构建这些模型。
从本地模式到全球模式
假设我们想要预测给定时间序列的未来值。我们使用时间序列的历史观察来建立训练集。然后,使用该数据选择并拟合预测模型。图 1 显示了这个过程的一个例子。
这是我们创建模型的典型操作方式。这方面的例子是经典的方法,如 ARIMA 或指数平滑。ARIMA 的 AR(自回归)部分将未来观测值建模为过去观测值的线性组合。自动回归也可以应用于非线性回归模型,如随机森林。MA 部分类似,但使用过去的误差。指数平滑法使用过去值的加权平均值。对于较老的观测值,权重呈指数衰减。
这些模型是局部的,因为训练数据只涉及一个时间序列。

图 1:澳大利亚啤酒产量的时间序列,蓝色部分是 ARIMA 对未来观测的预测。在这种情况下,在预测之前,使用过去的观察训练一个局部模型。图片作者。
但是,全球预测模型越来越受欢迎。这些汇集了多个时间序列的历史观测值,以符合预测模型。
全球预测模型不同于多元时间序列预测。在前者中,我们将许多时间序列的观察值(表中的行)连接起来。对于多元时间序列,我们包括额外的序列作为解释变量(表中的列)。ARIMAX 就是一个例子。
直觉和假设
通过对多个时间序列的学习,模型获得了更好的泛化能力。这将导致更好的预测性能。额外的可用时间序列代表更多的训练数据。机器学习方法通过更大的训练样本提高了它们的预测性能。你可以在之前的文章中读到更多。
全球预测模型有一个重要的假设。输入的时间序列彼此之间有些关联。这里有个例子。假设我的目标是预测给定位置的风速以产生能量。加上谷歌的股价对训练模型帮助不大。然而,添加从不同位置捕获的风速可能会有一些用处。
在应用全局模型之前对时间序列进行聚类可能是一个合理的想法。详见参考文献[3]。
成功的例子
全球方法的有用性在 M4 和 M5 的预报竞赛中得到了证明。表现最好的方法遵循这种训练策略。
从许多时间序列中交叉学习是训练深度神经网络的标准方法。示例包括 DeepAR 或 N-BEATS 等。使用其他算法也可以提高预测性能。M5 竞赛的获胜者采用了 LightGBM 全球战略。
把手放在某物或者某人身上
现在我们对全球预测模型的运作有了基本的了解。在这里,我将编写一个简单的全球预测模型来预测电力需求。
案例研究—电力需求预测

电力需求预测对于管理电网运行非常重要。例如,平衡能源的供求。这里我们将使用一组可用的时间序列。该数据集包含许多欧洲国家每小时的电力消耗数据。为简单起见,我们将重点关注三个国家:葡萄牙、法国和英国。
这是完整的代码,我将在下面解释。
我们首先读取数据,并将其准备成合适的格式(第 1–17 行)。这里,为了简洁起见,我跳过了基本的数据操作。我们创建函数 ts_as_supervised ,它将时间序列转换成表格格式。
对于每个时间序列,我们执行以下操作:
- 将数据分成训练集和测试集。在 2015 年 7 月 1 日之后收集的所有观察结果用于测试(第 32-34 行);
- 根据训练数据中计算的平均值对时间序列进行规范化(第 36–39 行)。这一步很重要,因为输入的时间序列通常有不同的尺度。在这个阶段,您可能还需要执行其他预处理步骤。例如,应用幂变换来稳定方差。你可以查看之前的一篇文章,在这篇文章中我回顾了最常见的预处理步骤;
- 应用函数 ts_as_supervised 将序列转换成表格格式(第 41–43 行)。
现在有趣的部分来了。我们将所有时间序列的观测值连接起来,以训练一个预测模型(第 46 行)。这是目前数据集的一个小样本。请注意,国家和指数列不用于培训。
在剩余的代码行(48-60)中,我们训练了一个岭回归模型,并将其应用于一个时间序列。
外卖
- 传统上,使用单个时间序列的历史观察来训练模型;
- 全球预测模型通过使用多个相关的时间序列训练模型来工作;
- 这些方法正在成为训练预测模型的标准方法。这些包括深度神经网络或梯度增强算法(例如 light GBM);
- 全球方法的成功例子包括 M4 和 M5 预测竞赛的获胜者。
进一步阅读
1 Hewamalage、Hansika、Christoph Bergmeir 和 Kasun Bandara。"时间序列预测的全球模型:模拟研究."模式识别 124 (2022): 108441。
[2] Makridakis,Spyros,Evangelos Spiliotis 和 Vassilios Assimakopoulos。" M5 精确度竞赛:结果、发现和结论."《国际预测杂志》 (2022)。
[3] Bandara、Kasun、Christoph Bergmeir 和 Slawek Smyl。"使用递归神经网络对相似序列组进行跨时间序列数据库的预测:聚类方法."专家系统及应用 140 (2020): 112896。
GraphSAGE:放大图形神经网络
原文:https://towardsdatascience.com/introduction-to-graphsage-in-python-a9e7f9ecf9d7
PyTorch 几何的 GraphSAGE 简介

图片作者,表情符号作者open moji(CC BY-SA 4.0)
UberEats 和 Pinterest 有什么共同点?
他们都使用 GraphSAGE 来为他们的推荐系统提供大规模的支持:数以百万计的节点和边。
- 🖼️ Pinterest 开发了自己的名为 PinSAGE 的版本,向用户推荐最相关的图片(pin)。
→他们的图有 180 亿个连接,30 亿个节点。 - 🍽️ UberEats 也报道了使用 GraphSAGE 的修改版来推荐菜肴、餐馆和美食。
→ UberEats 号称支持超过 60 万家餐厅,6600 万用户。
在本教程中,我们将使用具有 20k 个节点的数据集,而不是数十亿个节点,因为 Google Colab 无法处理我们的雄心。我们将坚持使用最初的 GraphSAGE 架构,但是之前的变体也带来了我们将讨论的令人兴奋的特性。
可以用下面的 Google Colab 笔记本运行代码。
🌐I. PubMed 数据集

考研的 t-SNE 情节(图片由作者提供)
在本文中,我们将使用 PubMed 数据集。正如我们在上一篇文章中看到的,PubMed 是小行星数据集的一部分(麻省理工学院许可)。这里有一个简短的总结:
- 它包含来自 PubMed 数据库的 19,717 篇关于糖尿病的科学出版物;
- 节点特征是TF-IDF500 维的加权词向量,这是一种无需变形金刚的高效文档汇总方式;
- 该任务是一个多级分类,具有三个类别:实验性糖尿病、1 型糖尿病和 2 型糖尿病。
这就是深度学习的美丽和诅咒:我对糖尿病一无所知,但如果我们达到 70%的准确率,我仍然会感到非常满意。至少我们不会制造下一个 IBM Watson。
Dataset: Pubmed()
-------------------
Number of graphs: 1
Number of nodes: 19717
Number of features: 500
Number of classes: 3Graph:
------
Training nodes: **60**
Evaluation nodes: **500**
Test nodes: **1000**
Edges are directed: False
Graph has isolated nodes: False
Graph has loops: False
正如我们所见,与整个图表相比,PubMed 的训练节点数量低得惊人。只有 60 个样本来学习如何对 1000 个测试节点进行分类。
尽管面临这一挑战,GNNs 仍设法获得高水平的准确性。以下是已知技术排行榜(更详尽的基准可以在的论文中找到,代码为):

我在 PubMed 上找不到 GraphSAGE 在这种特定设置下的任何结果(60 个训练节点,1000 个测试节点),所以我不期望有很高的准确性。但是在处理大型图表时,另一个度量标准也同样重要:训练时间。
🧙♂️二世。理论上的文法


作者图片
GraphSAGE 算法可以分为两个步骤:
- 邻居抽样;
- 聚合。
🎰a .邻居抽样
小批量是机器学习中常用的技术。
它通过将数据集 分解成更小的批次来工作,这允许我们更有效地训练模型。小批量有几个好处 :
- 提高精度 —小批量有助于减少过度拟合(梯度被平均),以及误差率的变化;
- 提高速度 —小批量并行处理,比大批量花费更少的训练时间;
- 改进的可扩展性 —整个数据集可以超过 GPU 内存,但较小的批处理可以绕过这一限制。
小型批处理非常有用,它已经成为常规神经网络的标准。然而,对于图形数据来说就不那么简单了,因为将数据集分割成更小的块会破坏节点之间的基本连接。
那么,我们能做什么呢?近年来,研究人员开发了不同的策略来创建图形小批量。我们感兴趣的那个叫做 邻居采样 。你可以在 PyG 的文档上找到很多其他技术,比如子图聚类。

邻居采样(图片由作者提供)
邻居采样只考虑随机邻居中固定数量的。流程是这样的:
- 我们定义邻居数量 (1 跳),邻居的邻居数量(2 跳)等等。我们想要。
- 采样器查看邻居列表、邻居的邻居列表等。并且随机选择预定数量的目标节点;
- 采样器输出包含目标节点和随机选择的邻居节点的子图。
对列表中的每个节点或整个图重复该过程。然而,为每个节点创建一个子图是没有效率的,这就是为什么我们可以成批处理它们。在这种情况下,每个子图由多个目标节点共享。
相邻采样还有一个额外的好处。有时,我们会观察到非常受欢迎的节点,这些节点就像枢纽一样,比如社交媒体上的名人。获得这些节点的隐藏向量在计算上是非常昂贵的,因为它需要计算数千甚至数百万个邻居的隐藏向量。GraphSAGE 通过简单地忽略大多数节点解决了这个问题!
在 PyG 中,邻居采样是通过[NeighborLoader](https://pytorch-geometric.readthedocs.io/en/latest/modules/loader.html#torch_geometric.loader.NeighborLoader)对象实现的。假设我们想要 5 个邻居和他们的 10 个邻居 ( num_neighbors)。正如我们所讨论的,我们还可以通过为多个目标节点创建子图来指定一个batch_size来加速这个过程。
**Subgraph 0**: Data(x=[389, 500], edge_index=[2, 448], batch_size=16)
**Subgraph 1**: Data(x=[264, 500], edge_index=[2, 314], batch_size=16)
**Subgraph 2**: Data(x=[283, 500], edge_index=[2, 330], batch_size=16)
**Subgraph 3**: Data(x=[189, 500], edge_index=[2, 229], batch_size=12)

我们创建了 4 个不同大小的子图。它允许我们并行处理它们,并且它们更容易安装在 GPU 上,因为它们更小。
邻居的数量是一个重要的参数,因为修剪我们的图形会删除很多信息。具体多少钱?嗯,相当多。我们可以通过查看节点度数(邻居数量)来形象化这种影响。

原始图中的节点度

邻居采样后的节点度数
在这个例子中,我们的子图的最大节点度是 5,这比最初的最大值低得多。在谈论 GraphSAGE 时,记住这种权衡是很重要的。
PinSAGE 使用随机游走实现另一种采样解决方案。它有两个主要目标:
- 样本 a 固定数量的邻居(如 graph sage);
- 获取它们的相对重要性(重要节点比其他节点更频繁出现)。
这个策略感觉有点像快速注意力机制。它为节点分配权重,并增加最受欢迎的节点的相关性。
💥b .汇总
聚集过程确定如何组合特征向量以产生节点嵌入。原始论文提出了三种聚合特征的方法:
- 表示聚合器;
- LSTM 聚合器;
- 汇集聚合器。

聚合(作者图片)
均值聚合器是最简单的一个。这个想法接近于 GCN 的方法:
- 目标节点及其所选邻居的隐藏特征被平均(ñᵢ);
- 应用具有加权矩阵𝐖的线性变换。

然后,可以将结果反馈给非线性激活函数,如 ReLU 。
LSTM 聚合器可能看起来是一个奇怪的想法,因为这个架构是顺序的:它给无序的节点分配一个顺序。这就是为什么作者随机打乱它们来迫使 LSTM 只考虑隐藏的特征。在他们的基准测试中,这是性能最好的技术。
汇集聚集器将每个邻居的隐藏向量馈送给前馈神经网络。对结果应用最大池操作。
🧠三世。PyTorch 几何中的 GraphSAGE
我们可以用 PyTorch Geometric 的SAGEConv层轻松实现 GraphSAGE 架构。这种实现使用两个权重矩阵,而不是一个,就像 UberEats 的 GraphSAGE 版本:

让我们创建一个具有两层SAGEConv的网络:
- 第一个将使用 ReLU 作为激活函数和一个脱落层;
- 第二个将直接输出节点嵌入。
由于我们正在处理多类分类任务,我们将使用交叉熵损失作为我们的损失函数。我还添加了一个 0.0005 的 L2 正则化值,以便更好地测量。
要了解 GraphSAGE 的优势,让我们将与GCN 和 GAT 进行比较,无需任何采样。
使用 GraphSAGE,我们遍历由邻居采样过程创建的批(我们的 4 个子图)。因此,我们计算准确度和验证损失的方法也不同。
以下是GCN、GAT 和 GraphSAGE 的结果(根据准确度和训练时间)😗***
****GCN** test accuracy: **78.40% (52.6 s)
GAT** test accuracy: **77.10% (18min 7s)
GraphSAGE** test accuracy: **77.20% (12.4 s)****
三个模型在精确度方面获得相似的结果。我们希望 GAT 性能更好,因为它的聚合机制更加细致,但情况并非总是如此。
真正的区别是训练时间:在这个例子中,GraphSAGE 比 GAT 快 88 倍,比 GCN 快 4 倍。
这就是 GraphSAGE 的真正力量。通过用邻居采样修剪我们的图,我们确实丢失了许多信息。最终的节点嵌入可能不如我们用 GCN 或 GAT 找到的好。但这不是重点:GraphSAGE 旨在提高可伸缩性。反过来,它可以构建更大的图表,从而提高准确性。

作者图片
这项工作是在有监督的训练环境中完成的(节点分类),但是我们也可以用无监督的方式训练 GraphSAGE。
在这种情况下,我们不能使用交叉熵损失。我们必须设计一个损失函数,迫使原始图中相邻的节点在嵌入空间中保持彼此靠近。相反,相同的函数必须确保图中的距离节点在嵌入空间中必须具有距离表示。这是 GraphSAGE 的论文中提出的损失。
在 PinSAGE 和 UberEeats 的改进 GraphSAGE 的例子中,我们正在处理推荐系统。
目标是为每个用户正确地排列最相关的项目(大头针,餐馆),这是非常不同的。我们不仅想知道最接近的嵌入是什么,我们还必须产生可能的最佳排名。这就是为什么这些系统也以无监督的方式训练,但具有另一个损失函数:最大利润排名损失。
结论
GraphSAGE 是一个处理大型图形的非常快速的架构。它可能没有 GCN 或 GAT 精确,但它是处理海量数据的基本模型。在本例中,它提供了这样的速度,这要归功于 1/邻居采样修剪图形和 2/快速聚合与平均聚合器的巧妙结合。在这篇文章中,
- 我们用 PubMed 探索了一个新数据集,比之前大了好几倍;
- 我们解释了邻居采样背后的思想,它在每一跳只考虑预定数量的随机邻居;
- 我们在 GraphSAGE 的论文中看到了三个聚合器,并专注于均值聚合器;
- 我们根据精度和训练时间对和三款车型(GraphSAGE、GAT 和 GCN)进行了基准测试。
我们看到了三种具有相同终端应用的架构:节点分类。但是 GNNs 已经成功应用于其他任务。在接下来的教程中,我想在两个不同的环境中使用它们:图形和边缘预测。这将是一个发现新的数据集和应用的好方法,在这些数据集和应用中,gnn 占据了最先进的水平。
如果你喜欢这篇文章,让我们在 Twitter 上联系@ maxime labanne以获得更多的图形学习内容。
感谢您的关注!📣
相关文章
**** ****
张量流图像分类简介(一)
Python 计算机视觉初学者实用指南
计算机视觉是人工智能的一个领域,它使机器能够将图像和视频等视觉数据处理成有意义的信息。图像分类是计算机视觉的一个普遍应用。在这篇文章中,我们将学习如何使用谷歌开发的开源深度学习库 TensorFlow 在 Python 中进行基本的图像分类。

📦数据
我们将使用手写数字的 MNIST 数据集,这是众所周知的介绍性图像数据集之一。这些数据可以在知识共享署名-同样分享 3.0 许可协议下获得。我们将加载必要的库和数据:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_splitimport tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras import Sequential
from tensorflow.keras.layers import (Flatten, Dense,
Conv2D, MaxPooling2D)import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid', context='talk')(train_data, train_labels), (test_data, test_labels) = mnist.load_data()
train_data, valid_data, train_labels, valid_labels = train_test_split(
train_data, train_labels, test_size=10000, random_state=42
)
print("========== Training data ==========")
print(f"Data shape: {train_data.shape}")
print(f"Label shape: {train_labels.shape}")
print(f"Unique labels: {np.unique(train_labels)}")print("\n========== Validation data ==========")
print(f"Data shape: {valid_data.shape}")
print(f"Label shape: {valid_labels.shape}")
print(f"Unique labels: {np.unique(valid_labels)}")print("\n========== Test data ==========")
print(f"Data shape: {test_data.shape}")
print(f"Label shape: {test_labels.shape}")
print(f"Unique labels: {np.unique(test_labels)}")

我们有 50K 的训练,10K 验证和 10K 测试 28×28 像素的图像。不出所料,有 10 类数字。现在让我们检查每个分区数据集的类分布:
n_classes = len(np.unique(train_labels))
(pd.concat([pd.Series(train_labels).value_counts(normalize=True)
.sort_index(),
pd.Series(valid_labels).value_counts(normalize=True)
.sort_index(),
pd.Series(test_labels).value_counts(normalize=True)
.sort_index()],
keys=['train', 'valid', 'test'], axis=1)
.style.background_gradient('YlGn', axis='index').format("{:.2%}"))

数据集之间的类分布相当均衡。如果你想学习如何像这样美化你的熊猫数据框,你可能会发现这个帖子很有用。
在我们开始构建图像分类模型之前,让我们通过检查一些样本图像来研究数据:
def inspect_sample_images(data, labels, title, n_rows=2, n_cols=3,
seed=42):
np.random.seed(seed)
indices = np.random.choice(range(len(data)), n_rows*n_cols,
replace=False)
plt.figure(figsize=(8,5))
for i, ind in enumerate(indices):
ax = plt.subplot(n_rows, n_cols, i+1)
plt.imshow(data[ind], cmap='binary')
plt.axis('off')
plt.title(f"Label: {labels[ind]}", fontsize=14)
plt.suptitle(title, fontsize=20)
plt.tight_layout();
inspect_sample_images(train_data, train_labels, 'Sample training images')

作者图片
我们可以看到图像反映了不同的笔迹。
inspect_sample_images(valid_data, valid_labels, 'Sample validation images')

作者图片
左下角的数字 8 被稍微切掉了。也许一些图像可能被裁剪。
inspect_sample_images(test_data, test_labels, 'Sample test images')

作者图片
在这个例子中有两个 2,他们都有自己的风格。
🔨系统模型化
这是令人兴奋的部分!由于模型构建过程是非常实验性和迭代性的,我们将迭代地构建两个模型。
🔧模型 0
目前,标签是 1D 数组格式。我们需要像这样对我们的标签进行一次热编码:

列标题仅用于说明|图片由作者提供
现在,让我们建立我们的第一个简单的神经网络。我们将为可复制性播下种子。
train_labels_ohe = tf.one_hot(train_labels, 10)
valid_labels_ohe = tf.one_hot(valid_labels, 10)
test_labels_ohe = tf.one_hot(test_labels, 10)tf.random.set_seed(42)
model_0 = Sequential([
Flatten(input_shape=(28, 28)),
Dense(16, activation="relu"),
Dense(16, activation="relu"),
Dense(n_classes, activation="softmax")
])model_0.compile(loss="categorical_crossentropy", optimizer='Adam',
metrics=["accuracy"])
model_0.summary()

这里我们首先定义了神经网络的架构,然后编译它并打印它的摘要。让我们仔细看看。
◼️ 在第一层flatten定义了神经网络 的架构,它将图像从(28,28) 2D 阵列展平到(784) 1D 阵列。然后,我们有两个完全连接的隐藏层(dense & dense_1)。对于这些层,我们使用 ReLu 激活函数。接下来是具有softmax激活功能(dense_2)的输出层,其单元数量与类别数量相同。
◼编译的模型 我们用的是categorical_crossentropy损失函数。输出层中的损失函数和 softmax 激活函数允许我们获得每个类别的概率,因为我们正在构建多类别分类模型。我们使用了Adam优化器。
◼打印出模型概要 一旦编译完成,我们可以从概要中看到模型的层数以及参数的数量。
现在,是时候训练网络了:
hist_0 = model_0.fit(train_data, train_labels_ohe, epochs=5,
validation_data=(valid_data, valid_labels_ohe))

为了更快的训练,我们将只做 5 个周期。这意味着网络将遍历数据 5 次。从上面的总结中,我们看到精度随着每个历元而提高。让我们想象一下各个时期的精确度:
def clean_history(hist):
epochs = len(hist.history['accuracy'])
df = pd.DataFrame(
{'epochs': np.tile(np.arange(epochs), 2),
'accuracy': hist.history['accuracy'] +
hist.history['val_accuracy'],
'loss': hist.history['loss'] +
hist.history['val_loss'],
'dataset': np.repeat(['train', 'valid'], epochs)}
)
return dfsns.lineplot(data=clean_history(hist_0), x='epochs', y='accuracy',
hue='dataset');

作者图片
我们已经创建了一个函数,因为这将有助于评估后续模型。我们将继续为其他评估方法构建函数。让我们根据看不见的测试数据来评估模型的性能:
test_preds_0 = model_0.predict(test_data)
test_classes_0 = test_preds_0.argmax(axis=1)
test_metrics = pd.DataFrame(columns=['Test accuracy'])
test_metrics.loc['model_0'] = np.mean(test_labels==test_classes_0)
test_metrics

太好了,我们将把后续型号的性能添加到这个数据框架中,这样我们就可以一目了然了。test_preds_0由(10000,10) 2D 数组组成,该数组包含每条记录的分类预测概率。然后,我们为每条记录分配概率最高的类别,并将其保存到test_classes_0中。现在,让我们看看混淆矩阵:
def show_confusion_matrix(labels, classes):
cm = (pd.crosstab(pd.Series(labels, name='actual'),
pd.Series(classes, name='predicted'))
.style.background_gradient('binary'))
return cmshow_confusion_matrix(test_labels, test_classes_0)

很高兴看到大多数记录都是沿着对角线从左上延伸到右下。有趣的是,目前的模型经常把 8s 和 2s 搞错。
让我们用它们的预测来检查一些示例图像:
def inspect_sample_predictions(data, labels, preds, dataset='test',
seed=42, n_rows=2, n_cols=3):
np.random.seed(seed)
indices = np.random.choice(range(len(data)), n_rows*n_cols,
replace=False)
plt.figure(figsize=(8,5))
for i, ind in enumerate(indices):
ax = plt.subplot(n_rows, n_cols, i+1)
plt.imshow(data[ind], cmap='binary')
plt.axis('off')
proba = preds[ind].max()
pred = preds[ind].argmax()
if pred == labels[ind]:
colour = 'green'
else:
colour = 'red'
plt.title(f"Prediction: {pred} ({proba:.1%})", fontsize=14,
color=colour)
plt.suptitle(f'Sample {dataset} images with prediction',
fontsize=20)
plt.tight_layout();
inspect_sample_predictions(test_data, test_labels, test_preds_0)

作者图片
我们现在来看看最不正确的预测(即最有可能的不正确预测):
def see_most_incorrect(data, labels, preds, dataset='test', seed=42,
n_rows=2, n_cols=3):
df = pd.DataFrame()
df['true_class'] = labels
df['pred_class'] = preds.argmax(axis=1)
df['proba'] = preds.max(axis=1)
incorrect_df = df.query("true_class!=pred_class")\
.nlargest(n_rows*n_cols, 'proba')
plt.figure(figsize=(8,5))
for i, (ind, row) in enumerate(incorrect_df.iterrows()):
ax = plt.subplot(n_rows, n_cols, i+1)
plt.imshow(data[ind], cmap='binary')
plt.axis('off')
true = int(row['true_class'])
proba = row['proba']
pred = int(row['pred_class'])
plt.title(f"Actual: {true} \nPrediction: {pred} ({proba:.1%})",
fontsize=14, color='red')
plt.suptitle(f'Most incorrect {dataset} images', fontsize=20)
plt.tight_layout();
see_most_incorrect(test_data, test_labels, test_preds_0)

作者图片
由于我们在打印概率时四舍五入到小数点后一位,所以这里的 100.0%很可能代表像 99.95 这样的概率..%或 99.99..%.这让我们看到了该模型肯定会出错的图像。即使对人类来说,第一个和最后一个图像也很难识别为 6。
让我们看看是否可以改进模型。
🔧模型 1
神经网络倾向于很好地处理介于 0 和 1 之间的数据。因此,我们将使用以下公式将数据重新调整到此范围:

由于像素值的范围在 0(最小值)和 255(最大值)之间,我们只需将值除以 255 即可缩放。除了重新调整,我们将保持其他一切和以前一样。一次改变一件事并理解它的影响是一个好习惯:
train_data_norm = train_data/255
valid_data_norm = valid_data/255
test_data_norm = test_data/255tf.random.set_seed(42)
model_1 = Sequential([
Flatten(input_shape=(28, 28)),
Dense(16, activation="relu"),
Dense(16, activation="relu"),
Dense(n_classes, activation="softmax")
])model_1.compile(loss="categorical_crossentropy", optimizer='Adam',
metrics=["accuracy"])
model_1.summary()

让我们根据重新调整后的数据训练编译后的模型:
hist_1 = model_1.fit(
train_data_norm, train_labels_ohe, epochs=5,
validation_data=(valid_data_norm, valid_labels_ohe)
)

通过简单的预处理步骤,性能比以前好了很多!现在,让我们来看看各个时期的表现:
sns.lineplot(data=clean_history(hist_1), x='epochs', y='accuracy',
hue='dataset');

作者图片
是时候根据测试数据评估模型并将其添加到我们的test_metrics数据框架中了。
test_preds_1 = model_1.predict(test_data_norm)
test_classes_1 = test_preds_1.argmax(axis=1)
test_metrics.loc['model_1'] = np.mean(test_labels==test_classes_1)
test_metrics

我们通过一个简单的改变大大提高了模型的预测能力。让我们用混淆矩阵更仔细地看看性能:
show_confusion_matrix(test_labels, test_classes_1)

现在,8 不再经常与 2 混淆。现在最常见的错误就是混淆了 4 和 9。这并不奇怪,因为在一些笔迹中,它们看起来确实很相似。
inspect_sample_predictions(test_data_norm, test_labels,
test_preds_1)

作者图片
当我们使用相同的种子来抽取随机图像时,我们看到的是与之前相同的图像子集。我们可以看到,一些先前预测不正确的图像现在被正确预测了。很高兴看到正确的图像具有高概率,而不正确的图像具有较低的概率。
see_most_incorrect(test_data_norm, test_labels, test_preds_1)

作者图片
我们可以看到仍有改进的余地。
让我们看看是否可以改进模型。
🔧模型 2
我们将使用model_1作为基础,并将隐藏层中的单元数量从 16 增加到 64:
tf.random.set_seed(42)model_2 = Sequential([
Flatten(input_shape=(28, 28)),
Dense(64, activation="relu"),
Dense(64, activation="relu"),
Dense(n_classes, activation="softmax")
])model_2.compile(loss="categorical_crossentropy", optimizer='Adam',
metrics=["accuracy"])model_2.summary()

由于增加了单位数量,我们现在有了更多的参数。
hist_2 = model_2.fit(
train_data_norm, train_labels_ohe, epochs=5,
validation_data=(valid_data_norm, valid_labels_ohe)
)

模型性能看起来比以前略好。
sns.lineplot(data=clean_history(hist_2), x='epochs', y='accuracy',
hue='dataset');

作者图片
在过去的两个时代中,该模型略有过度拟合。让我们根据测试数据评估模型:
test_preds_2 = model_2.predict(test_data_norm)
test_classes_2 = test_preds_2.argmax(axis=1)
test_metrics.loc['model_2'] = np.mean(test_labels==test_classes_2)
test_metrics

太棒了,很高兴看到我们仍然看到模型的改进。
show_confusion_matrix(test_labels, test_classes_2)

随着模型越来越精确,混淆矩阵看起来沿着对角线越来越集中,在剩余的单元中主要是浅灰色到白色的单元。
inspect_sample_predictions(test_data_norm, test_labels,
test_preds_2)

作者图片
现在,模型得到了所有正确的样本图像!
see_most_incorrect(test_data_norm, test_labels, test_preds_2)

作者图片
顶部中间的图像看起来很棘手,而其余的图像相对来说更容易被人类识别。
让我们看看我们是否能最后一次改进模型。
🔧模型 3
卷积神经网络(CNN)可以很好地处理图像数据。现在让我们用一个简单的 CNN 来分析我们的数据。
model_3 = Sequential([
Conv2D(32, 5, padding='same', activation='relu',
input_shape=(28,28,1)),
Conv2D(32, 5, padding='same', activation='relu'),
MaxPooling2D(),
Conv2D(32, 5, padding='same', activation='relu'),
Conv2D(32, 5, padding='same', activation='relu'),
MaxPooling2D(),
Flatten(),
Dense(128, activation='relu'),
Dense(n_classes, activation="softmax")
])model_3.compile(loss="categorical_crossentropy", optimizer='Adam',
metrics=["accuracy"])
model_3.summary()

我们现在有了更多的参数。让我们训练模型:
hist_3 = model_3.fit(
train_data_norm, train_labels_ohe, epochs=5,
validation_data=(valid_data_norm, valid_labels_ohe)
)

太棒了,性能似乎有轻微提高!
sns.lineplot(data=clean_history(hist_3), x='epochs', y='accuracy',
hue='dataset');

作者图片
我们可以看到,该模型是非常轻微的过度拟合。
test_preds_3 = model_3.predict(test_data_norm)
test_classes_3 = test_preds_3.argmax(axis=1)
test_metrics.loc['model_3'] = np.mean(test_labels==test_classes_3)
test_metrics

哇,我们已经达到 99%的准确率了!✨
show_confusion_matrix(test_labels, test_classes_3)

这是迄今为止最好看的混淆矩阵。我们看到对角线上有许多零,一些数字的对角线上有 1000+。
inspect_sample_predictions(test_data_norm, test_labels,
test_preds_3)

作者图片
像以前一样,样本图像被正确预测。
see_most_incorrect(test_data_norm, test_labels, test_preds_3)

作者图片
其中一些图像有点棘手,尤其是 6s 和 7。这些 6 中的一个似乎也被model_2认定为最不正确的预测之一。
为了节省时间,我们将在这里结束我们的模型实验。在实践中,几乎可以肯定的是,在我们确定一个模型之前,我们将不得不进行更多的迭代。在这篇文章中,我们看到每一次迭代都提高了我们的模型预测能力。然而,这在实践中并不总是正确的,因为一些实验想法并不奏效。这是正常的,只是实验方法的一种性质。
虽然我们只探索了四个模型中的几个想法,但我们可以通过无数种方式扩展实验来改进模型。这里有一些尝试改进模型的方法:
◼️增加层数
◼️改变激活函数
◼️训练更长时间(即更多的时期)
如果您正在处理众所周知的数据集,获得神经网络设计灵感的一种方法是查看领先的模型架构。例如,我们可以从代码为的论文中看到 MNIST 数据集上的领先模型。写这篇文章的时候,简单 CNN 的齐次系综正以 99.91 的准确率领先。如果你很好奇,你可以从论文中了解更多关于该模型及其架构的信息。此外,通常还有一个伴随代码来进一步挖掘。由于大多数领先的模型往往有很强的性能,你的选择不仅仅局限于顶级模型。
🔐保存模型
一旦我们对一个模型满意了,就有一个方便直观的方法来保存我们的模型:
model_3.save('model_3')

通过保存模型,我们可以在下次加载模型,并直接使用它来进行预测,而不必从头开始构建。加载的模型的性能将与我们刚刚训练的模型完全相同。
loaded_model_3 = tf.keras.models.load_model('model_3')
print(f"Test accuracy: {np.mean(loaded_model_3.predict(test_data_norm).argmax(axis=1)==test_labels):.1%}")

这就是这篇文章的全部内容!希望这篇文章简要介绍了使用 Tensorflow 构建基本的图像分类模型以及如何迭代改进结果。完成基本的图像分类后,我们将在系列的第 2 部分中通过查看更真实的图像来积累经验。

您想访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果您使用 我的推荐链接成为会员,您的一部分会费将直接用于支持我。
谢谢你看我的帖子。如果你感兴趣,这里有我的一些帖子的链接:
◼️️ 管道、ColumnTransformer 和 FeatureUnion 解释
◼️️ FeatureUnion、ColumnTransformer &管道用于预处理文本数据
◼️ 用这些提示丰富您的 Jupyter 笔记本
◼️ 用这些提示整理您的 Jupyter 笔记本
◼️ 解释 Scikit-用 SHAP 学习模型
◼️️ 在 scikit 中选择特性
再见🏃 💨
张量流图像分类简介(二)
Python 计算机视觉初学者实用指南
在系列的第一部分中,我们在包含手写数字黑白图像的 MNIST 数据集上构建了基本的图像分类模型。这些数据很容易通过 TensorFlow 获得。然而,在实践中,现实生活中的图像是丰富多彩的,数据通常不容易获得。在这篇文章中,我们将练习自己加载图像数据,并在彩色图像上建立模型。我们还将学习一点关于迁移学习的知识。

📦数据
我们将使用从蔬菜农场和市场收集的 15 种蔬菜的图像。关于该数据集的官方论文可在这里获得。数据集可通过 CC BY-SA 4.0 许可证获得。
如果您想跟随教程,请从蔬菜图像数据集| Kaggle 下载数据集,并将数据保存在与您的笔记本位于同一目录的名为data的文件夹中,并将validation子目录重命名为valid。完成后,您的工作目录将如下所示:
image_classification
├── *.ipynb
├── data
│ ├── train
│ │ ├── class 1
│ │ │ ├── image1.jpg
│ │ │ ├── ...
│ │ │ ├── imagen.jpg
│ │ ├── ...
│ │ ├── class n
│ │ │ ├── image1.jpg
│ │ │ ├── ...
│ │ │ ├── imagen.jpg
│ ├── valid
│ │ ├── class 1
│ │ │ ├── image1.jpg
│ │ │ ├── ...
│ │ │ ├── imagen.jpg
│ │ ├── ...
│ │ ├── class n
│ │ │ ├── image1.jpg
│ │ │ ├── ...
│ │ │ ├── imagen.jpg
│ ├── test
│ │ ├── class 1
│ │ │ ├── image1.jpg
│ │ │ ├── ...
│ │ │ ├── imagen.jpg
│ │ ├── ...
│ │ ├── class n
│ │ │ ├── image1.jpg
│ │ │ ├── ...
│ │ │ ├── imagen.jpg
这是组织图像的完美格式。每个分区数据集将不同类别的图像保存在以类别命名的单独子目录中。
我们现在将加载图像库并检查图像总数:
import pathlib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import seaborn as sns
sns.set(style='darkgrid', context='talk')import tensorflow as tf
from tensorflow.keras.preprocessing import image_dataset_from_directory
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (Input, Rescaling, Conv2D,
MaxPooling2D, Flatten, Dense)
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.applications.resnet import ResNet50images = [*pathlib.Path('data').glob('**/*.jpg')]
print(f"There are {len(images)} images.")

我们总共有 21K 张图片。让我们确认在分区的数据集中类名是相同的:
train_classes = [item.name for item in
pathlib.Path('data/train').glob('*')]
valid_classes = [item.name for item in
pathlib.Path('data/valid').glob('*')]
test_classes = [item.name for item in
pathlib.Path('data/test').glob('*')]if train_classes==valid_classes==test_classes:
print("All datasets have the same classes.")
print(f"There are total of {len(train_classes)} classes.")

太棒了,数据集上的类名都匹配。我们现在将创建 TensorFlow 数据集,它将在需要时批量加载数据。我们将混洗训练图像,以便在每一批中我们都有混合的蔬菜。
image_size = (224, 224)
shape = image_size + (3,)
batch_size = 32print("========== Training data ==========")
train_data = image_dataset_from_directory(
directory='data/train', label_mode='categorical',
image_size=image_size, batch_size=batch_size,
seed=42
)
print("\n========== Validation data ==========")
valid_data = image_dataset_from_directory(
directory='data/valid', label_mode='categorical',
image_size=image_size, batch_size=batch_size,
shuffle=False
)print("\n========== Test data ==========")
test_data = image_dataset_from_directory(
directory='data/test', label_mode='categorical',
image_size=image_size, batch_size=batch_size,
shuffle=False
)len(train_data.class_names)==len(valid_data.class_names)==len(test_data.class_names)

从图像的父目录推断图像的类名。我们看到这些推断的类名可以通过.class_names属性访问。让我们按类检查每个数据集的图像数量:
summary = pd.DataFrame()
for d in ['train', 'valid', 'test']:
for c in train_classes:
n = len([i for i in
pathlib.Path(f'data/{d}/{c}').glob('*.jpg')])
summary.loc[c, d] = n
summary.style.format("{:.0f}")

每堂课有 1000 个训练、200 个验证和 200 个测试图像。
现在,让我们在开始建模之前看看示例图像:
n_rows = 2
n_cols = 3train_path = pathlib.Path('data/train')
train_images = [item for item in train_path.glob('*/*.jpg')]
np.random.seed(42)
sample_images = np.random.choice(train_images, n_rows*n_cols,
replace=False)plt.figure(figsize=(12,8))
for i, image in enumerate(sample_images):
ax = plt.subplot(n_rows, n_cols, i+1)
plt.imshow(mpimg.imread(image))
plt.axis('off')
plt.title(image.parts[2])
plt.suptitle('Sample training images', fontsize=20);

🔨系统模型化
🔧型号 0
在第 1 部分中,我们构建的 CNN 架构被证明是 MNIST 数据集上表现最好的模型。因此,我们将从对蔬菜数据应用与基线模型相同的体系结构开始:
n_classes = len(train_data.class_names)model_0 = Sequential([
Rescaling(1./255, input_shape=shape),
Conv2D(32, 5, padding='same', activation='relu'),
Conv2D(32, 5, padding='same', activation='relu'),
MaxPooling2D(),
Conv2D(32, 5, padding='same', activation='relu'),
Conv2D(32, 5, padding='same', activation='relu'),
MaxPooling2D(),
Flatten(),
Dense(128, activation='relu'),
Dense(n_classes, activation='softmax')
])model_0.compile(loss='categorical_crossentropy', optimizer='Adam',
metrics=['accuracy'])
model_0.summary()

让我们训练网络。我们将只运行两个阶段,因为现在培训更加耗时:
hist_0 = model_0.fit(train_data, epochs=2,
validation_data=valid_data)

对于 15 个类别,大约 87%的准确率相当不错。让我们看看不同时期的准确性:
def clean_history(hist):
epochs = len(hist.history['accuracy'])
df = pd.DataFrame(
{'epochs': np.tile(np.arange(epochs), 2),
'accuracy': hist.history['accuracy'] +
hist.history['val_accuracy'],
'loss': hist.history['loss'] +
hist.history['val_loss'],
'dataset': np.repeat(['train', 'valid'], epochs)}
)
return dfsns.lineplot(data=clean_history(hist_0), x='epochs', y='accuracy',
hue='dataset');

我们将为每个测试图像准备标签。这将有助于进一步评估模型:
test_path = pathlib.Path('data/test')
test_images = [item for item in test_path.glob('*/*.jpg')]test_labels = []
for _, labels in test_data.unbatch():
test_labels.append(labels.numpy().argmax())
test_labels[:10]

现在,让我们根据测试数据检查模型的性能:
test_preds_0 = model_0.predict(test_data)
test_classes_0 = test_preds_0.argmax(axis=1)
test_metrics = pd.DataFrame(columns=['Test accuracy'])
test_metrics.loc['model_0'] = np.mean(test_labels==test_classes_0)
test_metrics

第一个模型约 87%的准确率是一个很好的开始。让我们通过课堂来理解表演:
def show_confusion_matrix(labels, classes):
cm = (pd.crosstab(pd.Series(labels, name='actual'),
pd.Series(classes, name='predicted'))
.style.background_gradient('binary'))
return cmshow_confusion_matrix(test_labels, test_classes_0)

很高兴看到大部分图像都集中在对角线上。在这里,我们选择不命名类,因为有空格(用类名代替数字会扩大表格的尺寸)。对角线上有一些较深的灰色单元格。例如,3 班有时会与 10 班混淆。有一种方法可以找到 3 级和 10 级的标签:
print(f"Class 3 is {train_data.class_names[3]}")
print(f"Class 10 is {train_data.class_names[10]}")

所以布林哈尔偶尔会被误认为是木瓜。现在,让我们看看一些示例图像及其预测:
def inspect_sample_predictions(images, preds, dataset='test',
seed=42, n_rows=2, n_cols=3):
np.random.seed(seed)
indices = np.random.choice(range(len(images)), n_rows*n_cols,
replace=False)
plt.figure(figsize=(12,8))
for i, index in enumerate(indices):
ax = plt.subplot(n_rows, n_cols, i+1)
image = images[index]
plt.imshow(mpimg.imread(image))
plt.axis('off')
proba = preds[index].max()
pred = preds[index].argmax()
pred_class = test_data.class_names[pred]
if pred_class == image.parts[2]:
colour = 'green'
else:
colour = 'red'
plt.title(f"Actual: {image.parts[2]} \nPredicted: {pred_class} ({proba:.1%})", color=colour, fontsize=14)
plt.suptitle(f'Sample {dataset} images with prediction', fontsize=20)
plt.tight_layout();
inspect_sample_predictions(test_images, test_preds_0)

很高兴看到所有这些样本图像都被正确预测。我们看到各种预测的置信度。现在,我们来看看最不正确的预测:
def see_most_incorrect(data, images, preds, dataset='test', seed=42,
n_rows=2, n_cols=3):
df = pd.DataFrame()
df['true_class'] = [image.parts[2] for image in images]
df['pred_class'] = [data.class_names[pred] for
pred in preds.argmax(axis=1)]
df['proba'] = preds.max(axis=1)
incorrect_df = df.query("true_class!=pred_class")\
.nlargest(n_rows*n_cols, 'proba')
plt.figure(figsize=(8,5))
for i, (ind, row) in enumerate(incorrect_df.iterrows()):
ax = plt.subplot(n_rows, n_cols, i+1)
plt.imshow(plt.imread(images[ind]), cmap='binary')
plt.axis('off')
true = row['true_class']
proba = row['proba']
pred = row['pred_class']
plt.title(f"Actual: {true}\nPred: {pred} ({proba:.1%})", fontsize=14, color='red')
plt.suptitle(f'Most incorrect {dataset} predictions', fontsize=20)
plt.tight_layout();
see_most_incorrect(test_data, test_images, test_preds_0)

在上面的中间图像中,您能马上分辨出是西红柿吗?西红柿的颜色不是你所期望的。
正如我们在上一篇文章中所了解到的,我们可以尝试通过添加层、更多单元、运行更多时代来增加模型的复杂性。如果模型对训练数据过拟合,在神经网络中加入脱落层有助于减少过拟合。或者,通过数据增强等技术获取更多数据或增加数据的多样性也有助于最小化过度拟合。数据增强是一种通过变换图像使训练数据多样化的技术。例如,作为数据增强的一部分,可以随机旋转、裁剪、平移、缩放和翻转图像。当没有更多数据可用时,通过数据增强使数据多样化可以帮助在更具普遍性的图像上训练模型。
让我们看看是否能改进这个模型。
🔧型号 1
到目前为止,我们一直在自己构建模型。然而,我们可以考虑另一种选择:迁移学习,在这种情况下,我们重用预先训练的模型。对于这个迭代,我们将使用迁移学习。特别是,我们将使用一种称为特征提取的迁移学习。在特征提取中,我们将保持预训练模型不变,只改变输出层以适应我们的用例。
我们将使用 ResNet-50 型号。该模型在通过 ImageNet 数据库获得的数千张图像上进行训练。在 TensorFlow 中,有几种不同的方法来加载预训练的模型。我们将使用下面的简单方法来加载一个模型。这里,我们指定 ResNet 模型不包括顶层,因为我们想要构建自己的输出层,它适用于 15 个蔬菜类。我们将在 ResNet 模型上添加展平层和输出层。
model_1 = Sequential([
ResNet50(include_top=False, weights='imagenet',
input_shape=shape),
Flatten(),
Dense(n_classes, activation='softmax')
])model_1.compile(optimizer=Adam(learning_rate=0.0001),
loss='categorical_crossentropy',
metrics=['accuracy'])
model_1.summary()

现在,我们将确保所有层都被设置为不可训练,以便从 ImageNet 学习的模型的权重和偏差保持不变。然后,我们将训练模型:
for layer in model_1.layers[0].layers:
layer.trainable=False
hist_1 = model_1.fit(train_data, validation_data=valid_data,
epochs=2)

哇,模型精度明显提高了!即使有 1 epoch,性能看起来也很棒。
让我们按时代来看性能:
sns.lineplot(data=clean_history(hist_1), x='epochs', y='accuracy',
hue='dataset');

随着第二个纪元,我们开始稍微过度拟合。
test_preds_1 = model_1.predict(test_data)
test_classes_1 = test_preds_1.argmax(axis=1)
test_metrics.loc['model_1'] = np.mean(test_labels==test_classes_1)
test_metrics

15 个类别的准确率达到 98%左右,这是一个很好的性能。让我们按类进一步挖掘:
show_confusion_matrix(test_labels, test_classes_1)

很高兴看到大多数值都集中在对角线上。通过重用预训练模型,我们可以达到事半功倍的效果。
inspect_sample_predictions(test_images, test_preds_1)

与上一次迭代一样,样本图像被正确预测。预测概率比以前更高。
see_most_incorrect(test_data, test_images, test_preds_1)

有趣的是,红底萝卜和胡萝卜被混淆了。这有助于了解模型的错误之处。
迁移学习也适用于较小的数据集(即较少的图像),只要您使用的模型是在相似的数据集上预先训练的。已经对迁移学习有了一个快速的介绍,如果你想了解更多关于迁移学习的知识,这个资源可能会有帮助。如果您想尝试其他预训练模型,请从这里查看其他可用模型。
这就是这篇文章的全部内容!希望你已经学到了一些实用的技能,可以开始你的计算机视觉之旅。如果你想通过在不同的数据集上应用我们作为系列的一部分所学的知识来获得更多的图像分类经验,Kaggle 中的这个开放数据集可能对你有用。

安娜·佩尔泽在 Unsplash 上的照片
您想访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果你使用 我的推荐链接 ,成为会员,你的一部分会费会直接去支持我。
谢谢你看我的帖子。如果你感兴趣,这里有我的一些帖子的链接:
◼️️ 管道、ColumnTransformer 和 FeatureUnion 讲解
◼️️ FeatureUnion、ColumnTransformer &管道用于预处理文本数据
◼️ 用这些提示丰富您的 Jupyter 笔记本
◼️ 用这些提示整理您的 Jupyter 笔记本
◼️ 讲解 Scikit-用 SHAP 学习模型
◼️️ 在 scikit 中选择特性
再见🏃 💨
图像嵌入和精度简介
原文:https://towardsdatascience.com/introduction-to-image-embedding-and-accuracy-53473e8965f
潜在空间的广泛介绍
通过聚类、线性判别分析和性能评估

前一章是嵌入、相似性和聚类的概述。本章通过扩展嵌入的概念将图像也包括在内,从而建立在这些基础之上。我们将探索在上一章中介绍的 K-Means 聚类在图像嵌入中的表现,并介绍通过准确度和召回率来衡量性能的方法。引入了一种简单的线性判别分析形式的潜在空间嵌入(LSE),以帮助我们理解聚类和性能是如何作用于图像的。本章不解释伦敦经济学院,因为它将在下一章介绍。
目录:
- 2.1 图像嵌入
- 2.2 集群性能
- 2.3 讨论
- 2.4 结论
2.1 图像嵌入
第 1 章解释了相同对象的不同嵌入如何适用于不同的应用。以图像形式嵌入书籍不适合书籍推荐,但需要以流派形式嵌入。然而,我们不能阅读使用体裁嵌入的书,因为这个应用程序需要实际的文本。一般来说,当使用嵌入时,目标是找到一种嵌入方法,为我们提供适合应用的相似性。
当寻找相似之处时,图像是一种复杂的嵌入形式。一个例子可能是一个用例,我们需要一个移动应用程序来帮助我们识别我们正在观察的动物。在这里,我们需要找到图像和动物物种之间的相似之处。

图 2.1 —作为数据集类别示例的动物图像:动物-10 。在 Unsplash 上 Amber Kipp 的猫照,在 Pexel 上 Valeria Boltneva 的狗照,在 Pexel 上 Erik Karits 的鸡照,在 Unsplash 上 Gabriel Porras 的牛照。
图像是我们在现实世界中看到的嵌入物。图像包括像素,每个像素是一种颜色。上面的图像是由 10.000 个这样的像素组合而成的。确切地说是图像)。每个像素提供了少量独特的信息,我们只能获得所有像素的完整图像。每个像素代表一种知识,因此应该有自己独特的维度。第一章展示了如何用两种体裁来表现一本书,给它两个维度。然而,一个 250x400 像素的灰度图像总共有 100.000 个维度!

图 2.2 —将图像转化为数字的过程。摘自第一章,图 1.1。由杰斯·贝利在 Unsplash 上拍摄的图书照片
那么问题就变成了,像素嵌入可以用来对图像中的动物进行分类吗?图 2.1 有四组动物(即猫、狗、鸡和牛)。从 Animal-10 数据集中提取每组的 16 幅图像,并放置在图 2.3 中的坐标系内(每组动物都有独特的颜色)。这些动物来自于ka ggle【1】上的 Animal-10 数据集,可以免费下载。

图 2.3 —图 2.1 中的四种动物在 2D 空间中各有 16 张图片。线性判别分析(LDA)用于将图像转换为 2D,因为它侧重于分类。
细心的读者可能会认识到,该图只显示了两个维度,而不是所有的 100.000。为了简单起见,我们从二维开始,然后将技术扩展到所有 100.000 维。关键要点是,彩色点没有清晰的单一颜色组,而是全部混合在一起。
主成分分析【2】(PCA)和线性判别分析【3】(LDA)都可以将一幅图像变换成 2D。PCA 侧重于转换没有类的数据,LDA 侧重于转换有类的数据。LDA 用于将图像转换为 2D,因为每幅图像中的动物是预先已知的。
图像嵌入部分的剩余部分解释了如何从图 2.3 生成图形。您可以继续阅读下一节 2.2 集群性能,跳过如何生成图表的详细说明
如何从图 2.3 中生成图形的详细说明: 图 2.3 中的点代表 Kaggle 上托管的 Animal-10 数据集的动物。该数据集可以在免费登录后从网站下载。下载按钮在图 2.4 中突出显示。

图 2.4—ka ggle 上的 Animal-10 数据集,突出显示了下载按钮。
将从 Kaggle 下载一个“存档”文件夹,由一个名为 raw-img 的文件夹和一个名为 translate.py 的文件组成。将 raw-img 文件夹放在已知位置;参见图 2.5。我们在代码 2.1 中定义了数据集的位置,以便以后可以访问它。

图 2.5 —将“raw-img”文件移动到已知位置。在本例中,它被移动到“数据集/动物/”文件夹中。代码 2.1 中提到了该位置。
代码 2.1 —定义动物 10 文件夹的位置
现在,我们可以通过定义动物的名称和要提取的图像数量,为每个动物组(即猫、狗、鸡和牛)提取十六个图像。代码 2.2 使用这些信息来遍历每个动物文件夹,加载 16 个图像并使它们大小相同。比较相同大小的图像更容易,并且是 LDA 等功能所必需的。
代码 2.2 —加载 16 张猫、狗、鸡和牛的图片。
我们现在可以为每只动物显示一张图片,以检查数据是否正确加载。每只动物显示一幅图像的代码可在代码 2.3 中找到。
代码 2.3 —为每只动物展示一张图片
之后,通过使用代码 2.4,可以使用线性判别分析 (LDA)将图像转换成 2D。在将图像转换到 2D 之前,必须“调整”LDA。拟合是教导 LDA 如何转换数据的过程。通过首先定义在变换完成后图像应该具有多少维度,然后给 LDA 哪些图像属于同一类的例子(即,给 LDA 图像并告诉其中是哪种动物)来进行拟合。它使用这些信息来计算如何转换这些和未来的图像。最后一步是使用 LDA 将我们的 16x4 图像转换为 2D。
代码 2.4 —将动物图像转换为 2D
2D 点可以用代码 2.5 绘制在如图 2.3 所示的图表中。
代码 2.5-从多个类中绘制 2D 点,每个点使用唯一的颜色。该图应与图 2.3 相匹配。
2.2 集群性能
我们在第 1 章中学习了如何使用 K-Means 将 k-clusters 应用于无色数据(没有类别/没有预定义动物的数据)。K-Means 侧重于在数据中找到 K 个组,并在每个组的中心放置一个点。该点被放置在中心以最好地代表其组,因为它到所有点的距离最短。我们的例子与 K-Means 略有不同,因为我们已经知道了组(相同动物物种的图像来自同一个组)。然而,K-Means 的想法可以通过在每个组/动物的中心放置一个点来最好地代表它。图 2.6 使用等式 2.1 添加了新的中心点。代码 2.6 显示了如何添加集群并绘制新的图表。 OBS!代码的输出不会突出显示带有黑色边框的集群。

图 2.6-每组/每只动物的中心都标有一个用黑色边框突出显示的新点。橙色:狗,红色:牛,绿色:鸡,蓝猫:

等式 2.1-计算组的中心
代码 2.6 —找到每个动物群的中心,并将其与其余动物群一起标绘
棘手的部分是,当我们得到一张新的图像并想弄清楚上面是哪种动物时会发生什么。图 2.7 显示了我们计算从新的变换图像到每个聚类的距离,因为每个聚类代表一个动物群。距离越小意味着相似度越高;使用欧几里德相似性选择最接近的聚类作为最佳拟合。等式 2.2 显示了如何计算两点之间的相似性。

图 2.7-测量到每个中心的距离,并选择最近的一个作为其组

等式 2.2-计算两点间欧几里得相似性得分的公式
那么,我们如何计算我们的集群识别动物的能力呢?一种简单的方法是首先忘记每个点属于哪个组,然后使用等式 2.2 来计算它们与哪个组最接近/最相似。图 2.8 和代码 2.7 使用这种方法来改变每个点的颜色,以匹配最近的聚类(继续阅读,很快就有意义了!).

图 2.8-点被重新分配,因此它们现在属于离它们最近的组
代码 2.7-通过计算最接近的类来分配点
当我们改变每个点的颜色以匹配最近的聚类时,会发生四种情况。我们举一个简单的例子来解释发生了什么。为了简单起见,这个例子使用了 Corona 测试而不是集群。电晕测试可以是阳性或阴性。当测试呈阳性时,会发生两种情况,要么是真的呈阳性,称为真阳性,,要么是假的,称为假阳性。如果测试为阴性,也会发生同样的事情,它可能是真的,称为真阴性,也可能是假的,称为假阴性。当测试得到更多的真阳性和真阴性时,我们更信任它。
我们的情况更复杂,因为我们有四个结果,而不是两个(即四个聚类/动物类型对阳性和阴性)。我们当时观察单个集群,以确定其性能如何。当我们确定它的性能时,同样的四种情况(真阳性、假阳性、真阴性和假阴性)也会发生。让我们关注蓝色的簇:前后都是蓝色的点被称为真阳性(TP) 。之前是另一种颜色但现在是蓝色的点被称为假阳性(FP) 。之前是蓝色但现在是另一种颜色的点被称为假阴性(FN)。最后,前后都是另一种颜色的点被称为真底片(TN) 。图 2.9 展示了关注蓝色集群时的每个场景。
我们必须为每个集群继续这一过程,以确定每个集群的表现如何。

图 2.9 —一个聚类的表现如何可以通过观察重新分配点后它有多少真阳性和假阳性以及真阴性和假阴性来衡量
我们在图 2.8 中使用的方法是有问题的,因为我们只使用现有的点来评估性能。这些点被用于创建/拟合 LDA,并且 LDA 自然更擅长获得这些正确的点。好消息是,我们在 Animal-10 数据集中有更多的图像可以测试!代码 2.8 显示了如何从每个动物类加载 128 张图片并转换到 2D。
代码 2.8-为每个动物类别加载 128 幅图像,并使用代码 2.2 和 2.4 中定义的函数将它们转换为 2D。
一个集群的表现如何可以通过精度和召回 [4]来衡量。 Precision 测量真阳性和假阳性之间的比率——因此,该聚类仅预测自己的点就有多好。回忆测量真阳性和假阴性之间的比率——因此,当预测时,聚类在包括其所有点方面有多好。公式 2.3 显示了精度公式,公式 2.4 显示了召回公式。代码 2.9 计算所有 512 个图像(128 个图像 pr)的每个聚类的阳性和阴性以及精确度和召回率。类)。

等式 2.3-计算聚类精度的公式。它是真阳性和假阳性之间的比率

等式 2.4 —计算聚类召回率的公式。它是真阳性和假阴性之间的比率
代码 2.9 —计算 2D 所有 512 个(128*4 类)图像的每个集群的性能
2D 图像的精度和召回率结果可在表 2.1 中找到。平均准确率 34%,平均召回率 33%,一点都不伟大!但是等等,我们只用了二维?让我们对所有 100.000 维尝试相同的计算,看看它是否比以前执行得更好。

表 2.1-使用 LDA 将点转换为 2D 时的精度和召回率
表 2.2 显示了所有 100.000 尺寸的结果以及代码 2.10 如何获得它们。平均准确率 31%,平均召回率 30%,比以前更差了!LDA 表现得更好,因为它专注于分离每个组,并使它们更加不同,即使只是一点点。

表 2.2-使用无任何变换的点时的精度和召回率
代码 2.10-在不转换数据的情况下使用所有维度时计算聚类准确度的代码
但是等等 LDA 只使用了两个维度…如果两者都转换数据并让它有更多的维度呢?LDA 的最大维数是“类数-1”,在我们的例子中是 4–1 = 3。表 2.3 显示了我们使用所有三个维度时的结果,代码 2.11 显示了如何获得它。平均准确率是 38%,平均召回率是 38%,仍然不是很好,但比以前好!

表 2.3-使用 LDA 将点转换为 3D 时的精度和召回率
代码 2.11-使用 LDA 将其转换为 3D 时用于计算聚类精度的代码
2.3 讨论
3D LDA 仅获得 38%的分数,因为它仅使用每组 16 幅图像来训练 LDA,并且因为它像未变换的图像一样,仅查看像素颜色。
当训练时,更多的图像给予 LDA 更多的上下文,因为它有更多的图像可以在图上绘制的例子。如果我们有 16 个图像而不是单个图像,则更容易判断来自同一组的未来点将在哪里。同样,128 张图片比 16 张图片给我们更多的信息。图 2.10 描绘了每组 128 幅图像;左图使用在 64 (164)幅图像上训练的 LDA,右图使用在 512 (1284)幅图像上训练的 LDA。该图说明了当在更多图像上训练时,LDA 如何更好地分离点。代码 2.11 显示了如何绘制这两个图形。
LDA 在转换图像时只查看像素颜色。这种方法有局限性,因为它优先考虑像素的颜色,而不是图像中的形状。这意味着棕色的猫和棕色的狗会比白色的狗和棕色的猫更相似!

图 2.10–512 张动物图像,使用 LDA 对每组 16 张图像(左)和每组 128 张图像(右)进行训练
代码 2.11 —创建一个新的 LDA,它将图像转换为 2D,并用每组 128 幅图像进行训练。
在第 3 章图像潜在空间嵌入简介中,我们将利用我们所了解的图像嵌入和 LDA 的优缺点来提高动物识别应用的准确性。
2.4 结论
我们现在已经学完了第 2 章,以及图像嵌入和精确度的介绍。
第一部分,图像嵌入,解释了图像由像素组成,每个像素代表一种颜色。每种颜色都是一个维度,就像第一章中的“人生旅程”和“小说”是书籍的维度。我们使用了来自四个动物群体(即猫、狗、鸡和牛)的 64 幅图像来展示如何在 Python 中嵌入图像。使用线性判别分析(LDA)将每幅图像转换成 2D,从而可以将它们绘制成图表并显示它们的相似性。
第二部分,集群性能,看我们如何通过在每个动物群的中心放置一个点来表示它们。这些点可以通过计算图像最接近哪个中心来帮助识别图像中的动物。精确度和召回率是确定一种方法如何识别正确动物的两种方法。Precision 检查有多少图像被识别为正确的动物,而 Recall 检查每个中心在包括其物种的所有图像方面有多好。对没有转换的图像和使用 LDAs 转换为 2D 和 3D 的图像进行了性能测试。
精度和召回率显示用 LDA 转换的图像比未转换的图像更好地识别正确的动物种类(38%对 31%)。原因是 LDA 变换图像以使每个组更明显。然而,图像和 LDA 只看颜色,而不看图像中的内容的形式。这意味着两种方法都认为棕色的猫和棕色的狗比棕色的狗和白色的狗更相似。
第 3 章,图像潜在空间嵌入简介,利用我们从图像嵌入和 LDA 的优缺点中学到的知识来提高动物识别应用的准确性。第三章利用变换的强度,解决了只看颜色的问题,达到了更好的精度。
参考
[2] Casey Cheng,用零数学直观地解释主成分分析法 (2022),
[3]杨,,线性判别分析, (2020)解释
[4]谷歌开发者,分类:精度与召回 (2022),developers.google.com
所有图片和代码,除非另有说明,均为作者所有。
感谢你阅读这本关于潜在空间的书!当分享我们的想法时,我们学得最好,所以请分享一个评论,无论是一个问题,新的见解,还是一个分歧。任何建议和改进都非常感谢!
如果你喜欢这本书,并且对机器学习和数据科学的新见解感兴趣,请注册中级会员,以便完全访问我的内容。关注我,以便在我发布新章节或帖子时收到电子邮件。
https://medium.com/@mathiasgronne/membership
书籍章节
简介:进入图像嵌入和潜在空间
第 1 章:嵌入、聚类和相似性介绍
第 2 章:图像嵌入和精度介绍
工具变量估计导论
原文:https://towardsdatascience.com/introduction-to-instrumental-variables-estimation-3eba75a04418

图片由 Alexa 发自 Pixabay ( Pixabay 授权)
我们将学习工具变量,以及如何使用它们来估计线性回归模型
在本文中,我们将了解一种使用称为工具变量的工件来估计线性回归模型的巧妙技术。
基于工具变量(IV)的估计允许回归建模者处理困扰大部分回归模型的严重情况,即一个或多个回归变量与模型的误差项相关。换句话说, 回归变量是内生的 。
由于一种被称为 省略变量偏差 的现象,对包含内生变量的模型的普通最小二乘估计会产生回归系数的有偏估计。从功能上讲,系数的这种偏差给实验者带来了一大堆实际问题,并可能使整个实验的有用性受到质疑。
在 IV 估计中,我们使用一个或多个工具变量 ( 工具 ) 代替可疑内生变量,并且我们使用被称为二阶段最小二乘法(简称 2SLS 的最小二乘法的修正形式来估计最终模型。
在本文的其余部分,我们将展示如何使用工具变量来减轻内生性的影响,并且我们将通过一个例子来说明工具变量的使用。
内生性及其后果
在上一篇文章中,我们学习了 外生变量和内生变量 。让我们快速回顾一下这些概念。
考虑以下线性模型:

两个变量的线性模型 x _2 和 x _3(图片由作者提供)
上式中, y 为因变量, x _2 和x_ 3为解释变量,【ϵ为误差项,捕捉到了yx对于包含 n 行的数据集, 1,y ,x_ 2,x_ 3,【ϵ都是大小为【n×1】—的列向量为了简洁起见,我们将从后面的等式中去掉 1 (它是 1 的向量)。
如果一个或多个回归变量,比如说x_ 3,是内生的,即它与误差项相关,则 OLS 估计量是而不是 一致的 。由于一种被称为 省略变量偏差 的现象,所有变量的系数估计值,而不仅仅是 x _3、的系数估计值都偏离真实值。
面对内生性,无论你的数据集有多大或者有多平衡,这种估计偏差都不会消失。
当面临模型的内生性时,您有以下选择:
- 如果内生性被怀疑很小,你可以简单地接受它和随之而来的系数估计偏差。
- 对于隐藏在您怀疑与内生变量相关的误差项中的不可观察因素,您可以选择合适的 代理变量 。
- 如果可疑的内生变量是时间不变的,即它们的值不随时间变化,那么对模型进行一次时间差分,就会将它们减去。这是一种主要在 面板数据模型 中起作用的策略。
- 您可以使用工具变量(IV)代替可疑内生变量,并使用 IV 估计技术(如 2 阶段最小二乘法)估计“工具化”模型。
让我们进一步了解如何使用 IVs。
开发工具变量的动机
再次考虑等式(1)中的模型:

二元线性模型 x _2 和 x _3(图片由作者提供)
在我们开始之前,让我们注意以下几点:
如果x_ 2和x_ 3都是外生的,则 OLS 估计量是一致的,它产生的所有系数估计都是无偏的。不需要基于 IV 的估计。
但是现在假设x_ 3是内生的。我们将概念性地将x_ 3的方差视为由两部分组成:
- 与 ϵ 不相关的一大块。这是x_ 3的部分,其实就是外生的。
- 与 ϵ.相关的第二个块 这是x_ 3内生的部分。
工具变量背后的关键直觉
如果我们能够以某种方式分离出x_ 3的外生部分,并用这个外生块替换x_ 3,同时从模型中省去x***_ 3的内生部分,则得到模型这是在线性模型中使用工具变量背后的关键直觉。***
为此,假设我们能够识别一个回归变量z_ 3,使得z_ 3具有以下两个属性:
- z _3 与 x _3 相关。记数法:Cov(z_ 3,x_ 3)!= 0.这就是所谓的关联条件,用于包含z_ 3。简单来说,z_ 3应该和 x _3 相关。**
- _ 3与误差项不相关,即 e(【ϵ|x】_ 3)= 0或常数,e(【ϵ】x这就是使用z*_ 3的外生条件。********
如果 z _3 满足相关条件和外生条件,则 z** _3 称为仪器变量或仪器为 x _2。**
不过先来考察一下关于z**_ 3的一个微妙点。**
由于z_ 3与x_ 3,我们可以将x_ 3表示为z**_ 3和一个误差的线性组合**

x _3 对 z _3 的回归(图片由作者提供)
然而, z _3 也可以与 x _2 相关联。回归变量之间的这种共线性在现实环境中非常普遍。由于这种共线性,x_ 2可能会通过z_ 3影响x_ 3并且在上面的等式中, γ_3 捕捉的不仅仅是z_ 3
我们可能要隔离出x_ 2对x_ 3的影响,这样 的主要影响 对**_ 3的影响为此,我们必须回归x_ 3不仅仅是在 z _3、上,还要在×x_ 2上:****

x _3 表示为 x _2 和 z _3 的线性组合,以及一个常数(图片由作者提供)
和前面一样,x_ 3,x_ 2,z_ 3,和误差项 ν 都是大小为的列向量同样,在等式(1a)中,x_ 2和z*_ 3是外生的,即它们与 ν 不相关。*****
将等式(1a)代入等式(1):

将等式(1a)代入等式(1)(图片由作者提供)
绿色位可以被吸收到新的截距 β_1 中。同样,我们将把( β_2+β_3 γ_2) 替换为新的系数 β_2 ,将 (β_3 γ_3)* 替换为 β3* ,并将复合误差项*(β 3 ν)+ϵ*
通过这些替换,等式(1)中的回归模型被转换成以下模型:

内生变量 x _3 替换为变量 z _3 的线性模型(图片由作者提供)
回想一下x_ 2和z_ 3都是假设不相关的和 ϵ 。因此,它们也与合成误差 、ϵ 不相关。因此,在等式(1b)中,R.H.S .上的所有变量都是外生的,等式(1b)可以使用 OLS 一致地估计。***
一个例子
考虑以下终生收入的治疗效果模型,该模型根据一个人是否上过常春藤盟校而回归:

终生收入随着这个人是否上过常春藤盟校而递减
布尔变量Attended _ Ivy _ League显然是内生的。一个人是否上过常春藤盟校取决于社会经济因素和个人特定因素,如能力和追求成功的动力,这些因素无法衡量,但也会直接影响一生的收入。因此,这些不可观察的变量隐藏在误差项中,并且它们还与Attended _ Ivy _ League,相关,使得Attended _ Ivy _ League内生。使用 OLS 估计会导致对 β_1 和 β_2 的有偏估计。**
很难想象更大的成功欲望会导致常春藤盟校的系统性下降。能力等其他因素也是如此。因此,我们假设【ϵ】和 中的隐藏因素之间存在正相关,从而导致对β_1 和β_ 2的正偏差,进而导致实验者的高估了常春藤盟校出勤率对终生收入的影响。********
显然,这个问题需要一个解决方案。让我们试着找出一个与Attended _ Ivy _ League相关但与误差项不相关的变量。其中一个变量是遗产,即这个人的父母或祖父母是否曾就读于常春藤联盟学校。数据显示,一个人的遗产状况和这个人是否被同一所常春藤盟校录取之间存在相关性。这满足了关联条件。此外,此人的父母或祖父母是否曾就读于常春藤联盟学校似乎与此人的能力和动机等因素没有直接关联,从而满足了外生性条件。因此,我们有:**
Cov( 遗留 , 出席 _ 常春藤 )!= 0
Cov( 遗留 ,ϵ)= 0
因此,我们指定 遗产 为 就读 _ 常春藤 的乐器。我们将评估以下测量模型,而不是原始模型:

回归模型的工具化版本(图片由作者提供)
使用 OLS 可以一致地估计这个仪器模型,并且可以得到 β_1 和 β_2 的无偏估计。注意 β_2 是 遗留 的效果,而不是Attended _ Ivy _ LeagueonLifetime _ income。 但是我们已经看到 β_2 无论如何都无法可靠估计。所以我们必须接受 β_2* 作为 β_2 的代表。顺带一提,估算软件会将 β_2* 报为原内生变量的系数 Attended_Ivy_League 和的系数遗留**** 这是好事。*
工具变量估计的优点和缺点
通过检查等式(1b)中的仪表化模型,有一点变得很明显:

内生变量 x _3 替换为变量 z _3 的线性模型(图片由作者提供)
仪表化变量( z _3 )与内生变量 x _3 并不完全相关。通过使用z_ 3代替**_ 3,我们正在丢失包含在x_ 3中的一些信息。这种信息损失使得仪表化模型具有更大的误差项和更不精确的系数估计,即它们的标准误差大于原始模型中相应系数的标准误差。较大的标准误差转化为较宽的置信区间。**
当我们使用工具变量进行估计时,我们是在用精度换取系数估计的无偏性——这是我们从模型中去除内生性所必须付出的代价。
总的来说,IV 估计模型不如内生模型精确。除非选择的变量与内生变量具有中等至强相关性,并且变量也或多或少与误差不相关,否则最好使用包含内生变量的原始模型,并接受其估计系数中的偏差。
对于回归建模者来说,虽然内生性的幽灵可能会令人困扰,但不得不与弱工具一起工作的前景也可能是同样糟糕的困境。
一种实用的 IV 估计方法
在现实生活中,人们可能希望采用以下方法:
- 使用 OLS 估计原始模型,使用 2SLS 等技术估计仪表化模型。
- 比较两个模型的估计系数值,并比较它们各自的置信区间。如果 OLS 估计模型的配置项完全位于 IV 估计模型的相应配置项内,这是使用 OLS 估计模型的一个强烈信号。
- 还要在人们正在研究的真实世界环境中检查来自两个模型的估计系数。根据上下文,选择产生较低(或较高)估计值的模型会有所帮助。
总结和关键要点
- 当一个回归模型包含一个或多个内生变量时,不能使用 OLS 进行一致的估计。如果使用 OLS 估计,估计的系数偏离它们的真实值。
- 当面临内生性时,人们可以要么接受由此产生的估计偏差,要么尝试使用诸如代理变量,或差分(在面板数据模型中),或通过工具变量 (IVs)等技术来修正它。
- 在 IV 方法中,我们确定与内生变量相关但与误差项不相关的合适变量,然后用 IV 替换内生变量。由此产生的模型是免费的内生解释变量,它可以估计使用 OLS。在实践中,我们使用特定于 IV 的技术,如 2 阶段最小二乘法 (2SLS)。
- IV 估计模型不如包含内生变量的原始模型精确,但与后者不同,它们产生无偏的系数估计。
参考文献、引文和版权
形象
本文中的所有图片版权归 CC-BY-NC-SA 下的 Sachin Date 所有,除非图片下面提到了不同的来源和版权。
如果您喜欢这篇文章,请关注我的Sachin Date以获得关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。
Python 中的线性编程简介
原文:https://towardsdatascience.com/introduction-to-linear-programming-in-python-9261e7eb44b
使用谷歌或工具进行数学优化的指南

图片由作者提供,表情符号由open moji(CC BY-SA 4.0)
线性规划是一种技术优化任何问题与多个变量和约束。这是一个简单但强大的工具,每个数据科学家都应该掌握。
想象你是一名军师正在招募一支军队。你有:
- 三种资源:🌾粮食,🪵 木材,🪙 黄金
- 三个单位 : 🗡️ 剑士,🏹弓手,以及🐎骑兵。
骑兵比弓箭手强壮,弓箭手又比剑士强壮。下表提供了每个装置的成本和功率:

作者图片
现在我们有 1200 个🌾食物,800 🪵wood,600 🪙gold.考虑到这些资源,我们应该如何最大化我们军队的力量?
我们可以简单地找到具有最佳功率/成本比的单元,尽可能多地采用它们,并对其他两个单元重复该过程。但是这种“猜测和检查”的解决方案可能甚至不是最佳的
现在想象我们有百万台,资源:之前的贪婪策略很可能完全错过最优解。可以使用机器学习算法(例如,遗传算法)来解决这个问题,但是我们也不能保证解决方案是最优的。
对我们来说幸运的是,有一种方法可以用最优的方式解决我们的问题:线性规划(或者线性优化),这是运筹学(或者)领域的一部分。在这篇文章中,我们将使用它来寻找最佳数量的剑士、弓箭手和骑兵,以建立可能拥有最高力量的军队。
你可以用下面的 Google Colab 笔记本 运行本教程的代码。
🧠一世:解决者
在 Python 中,有不同的线性编程库,比如多用途的 SciPy ,初学者友好的 PuLP ,详尽的 Pyomo 等等。
今天,我们将使用 谷歌或-Tools ,它非常用户友好,带有几个预打包的解算器,并且在 GitHub 上拥有迄今为止最多的明星。
如果安装不成功,请重启内核并重试:它有时会失败。_(ツ)_/
所有这些库都有一个隐藏的好处:它们充当到的接口,使用相同的模型和不同的解算器。像 Gurobi 、 Cplex 或 SCIP 这样的解算器有它们自己的 API,但是它们创建的模型被绑定到一个特定的解算器。
OR-Tools 允许我们使用一种抽象的(相当 pythonic 化的)方式来建模我们的问题。然后我们可以选择一个或几个求解器来找到一个最优解。因此,我们构建的模型是高度可重用的!

作者图片
OR-Tools 自带线性规划求解器,名为 GLOP (谷歌线性优化包)。它是由 Google 的运筹学团队创建的开源项目,用 C++编写。
其他解算器也可用,如 SCIP ,这是一个优秀的非商业解算器,创建于 2005 年,并更新和维护至今。我们也可以使用流行的商业选项,如 Gurobi 和 Cplex。然而,我们需要将它们安装在 OR-Tools 之上,并获得适当的许可证(这可能相当昂贵)。现在,让我们试试 GLOP。
🧮二世。变量
我们使用 GLOP 创建了一个 OR-Tools 解算器的实例。现在,如何使用线性规划?我们首先要定义的是我们要优化的变量。
在我们的例子中,我们有三个变量:🗡️swordsmen 的数量,🏹弓手,和🐎军队中的骑兵。OR-Tools 接受三种类型的变量:
NumVar为连续的变量;IntVar为整数变量;BoolVar为布尔变量。
我们正在寻找单位的整数,所以我们选择IntVar。然后,我们需要为这些变量指定下限和上限。我们想要至少 0 个单位,但是我们没有上限。所以我们可以说我们的上限是无穷大(或者任何我们永远达不到的大数字)。它可以写成:

让我们把它翻译成代码。在 OR-Tools 中无穷大被替换为solver.infinity()。除此之外,语法非常简单:
⛓️三世。限制
我们定义了变量,但是约束同样重要。
也许与直觉相反,添加更多约束有助于求解器更快地找到最优解。为什么会这样呢?把求解器想象成一棵树:约束帮助它修剪树枝,减少搜索空间。
在我们的例子中,我们可以用来生产单位的资源数量有限。换句话说,我们不能花费比我们拥有的更多的资源。例如,在🌾招募单位花费的食物不能高于 1200。🪵wood (800)和🪙gold (600)也是如此。
根据我们的表格,单位成本如下:
- 1 剑客 =🌾60 + 🪵20;
- 1 鲍曼 =🌾80 + 🪵10 + 🪙40;
- 1 骑士 =🌾140 + 🪙100.
我们可以为每个资源编写一个约束,如下所示:

在 OR-Tools 中,我们简单地用solver.Add()将约束添加到我们的求解器实例中。
🎯四。目标
现在我们有了变量和约束,我们想要定义我们的目标(或者目标函数)。
在线性编程中,这个函数必须是线性的(像约束一样),所以形式为 ax + by + cz + d 。在我们的例子中,目标非常明确:我们希望招募拥有最高权力的军队。该表给出了以下功率值:
- 1 剑客 =💪70;
- 1 鲍曼 =💪95;
- 1 骑士 =💪230.
最大化陆军的力量相当于最大化各部队力量的总和。我们的目标函数可以写成:

一般来说,目标函数只有两种:最大化或最小化。在 OR-Tools 中,我们用[solver.Maximize()](https://google.github.io/or-tools/python/ortools/linear_solver/pywraplp.html#Solver.Maximize)或[solver.Minimize()](https://google.github.io/or-tools/python/ortools/linear_solver/pywraplp.html#Solver.Minimize)来声明这个目标。
我们完事了。对任何线性优化问题建模有三个步骤:
- 声明变量用下限和上限进行优化;
- 将约束添加到这些变量中;
- 定义目标函数最大化或最小化。
现在很清楚了,我们可以要求求解器为我们找到一个最优解。
🥇五、优化!
用[solver.Solve(](https://google.github.io/or-tools/python/ortools/linear_solver/pywraplp.html#Solver.Solve))计算最优解。该函数返回一个状态,可用于检查解决方案是否确实是最优的。
让我们用最好的陆军配置打印出我们能得到的最高总功率。
================= Solution =================
Solved in 87.00 milliseconds in 2 iterations
**Optimal power** = 1800.0 💪power
Army:
- 🗡️**Swordsmen** = 6.0000000000000036
- 🏹**Bowmen** = 0.0
- 🐎**Horsemen** = 5.999999999999999
太好了!求解器找到了一个最优解:我军的总兵力为💪1800 带 6 个🗡️swordsmen 和 6 个🐎骑兵(对不起弓箭手!).
让我们解开这个结果:
- 求解器决定采用的最大数量🐎骑士 (6,因为我们只有🪙600,他们每个人都花了🪙100);
- 剩下的资源都花在🗡️ 剑士身上:我们有 1200–6 * 140 = 360🌾食物剩余,这就是为什么求解器选择 6 🗡️swordsmen;
- 我们可以推断出骑兵是最好的单位,弓手是最差的单位,因为他们根本没有被选中。
好吧,但是有一点很奇怪:这些数字不是圆的,尽管我们指定我们想要的是整数。发生了什么事?
不幸的是,回答这个问题需要深入研究线性编程…为了在这个介绍中保持简单,让我们说是因为 GLOP。解算器有我们必须考虑的特征,GLOP 不处理整数。这是构建可重用模型不仅仅是方便的另一个证明。
我们将在更高级的教程中解释为什么 GLOP 会有这种奇怪的行为以及如何修复它。
结论
通过这个例子,我们看到了任何线性优化问题的五个主要步骤:
- 选择求解器:在我们的例子中,为了方便起见,我们选择了 GLOP。
- 声明变量:要优化的参数是剑士、弓手和骑兵的数量。
- 声明约束:这些单元中的每一个都有成本。总费用不能超过我们有限的资源。
- 定义目标:最大化的标准是这支军队的总实力。也可能是其他东西,比如单位的数量。
- 优化 : GLOP 不到一秒钟就找到了这个问题的最优解。

作者图片
这是线性规划的主要好处:算法给我们一个保证,即找到的解是 最优(有一定误差)。这种保证是强有力的,但也是有代价的:模型可能非常复杂,以至于求解者需要数年(或更长时间)才能找到最优解。在这种情况下,我们有两种选择:
- 我们可以在一段时间后停止求解器(并可能获得一个次优答案);
- 我们可以使用类似遗传算法的元启发式算法在短时间内计算出一个优秀的解决方案。
在下一篇文章中,我们将讨论不同类型的优化问题,并将我们的方法推广到一整类优化问题。
我希望你喜欢这个介绍!欢迎分享,传播关于线性优化的知识。别忘了查看我的博客 和 在 Twitter 上关注我,我会在那里发布这些文章的摘要。干杯!
相关文章
逻辑回归简介:预测糖尿病
原文:https://towardsdatascience.com/introduction-to-logistic-regression-predicting-diabetes-bc3a88f1e60e
UCL 数据科学学会研讨会 11:什么是逻辑回归、数据探索、实施和评估

今年,作为 UCL 数据科学协会的科学负责人,该协会的目标是在整个学年举办一系列 20 场研讨会,涵盖的主题包括 Python、数据科学家工具包和机器学习方法的介绍。每一篇文章的目标都是创建一系列的小博客,这些小博客将概述要点,并为任何希望跟进的人提供完整研讨会的链接。所有这些都可以在我们的 GitHub 资源库中找到,该资源库将在全年更新新的研讨会和挑战。
本系列的第 11 个研讨会是 Seda Radoykova 撰写的 Python 中的逻辑回归介绍。本课程涵盖了什么是逻辑回归,什么时候可以使用它,探索数据,实施模型和评估结果。虽然亮点将会在这篇博文中呈现,但是完整的研讨会可以在我们的 GitHub 账户这里找到。
如果您错过了我们之前的任何研讨会,可以在这里找到:
什么是逻辑回归?
数据可以大致分为连续数据和分类/离散数据,前者可以在给定范围(如距离或时间)内获取无限数量的点,后者在给定数据组(如支付方式或客户投诉)内包含有限数量的点或类别。我们已经看到了以线性回归的形式将回归应用于连续预测问题的例子,其中我们预测了销售额,但是为了预测分类输出,我们可以使用逻辑回归。
虽然我们仍然使用回归来预测结果,但逻辑回归的主要目的是能够预测哪个类别和观察值属于哪个类别,而不是一个确切的值。该方法可用于的问题示例包括:“给定一个人的年龄、性别、吸烟状况、等、 ( 变量/特征),该人患疾病的可能性有多大?”“这封电子邮件是垃圾邮件的可能性有多大?”"给学生一些成绩预测指标,他们会通过考试吗?"。
逻辑回归的实现是基于“sigmoid 函数”,也称为“逻辑函数”,而不是线性回归中使用的线性函数。对于二元分类任务,其基础是结果值只能取 0 或 1,因此我们必须将我们的预测限制在这个范围内。该功能采取以下形式:

作者图片
用于将预测值映射到 0 和 1 之间的概率。这可以直观地表示为:
# implement a sigmoid function by hand
def sigmoid(x):
a = []
for item in x:
a.append(1/(1+math.exp(-item)))
return a# evaluate the sigmoid at some x values
sigm = np.arange(-22, 22, 0.5)# plot the sigmoid
plt.plot(sigm*0.2+4.57, np.array(sigmoid(sigm)), color = "red") # manually implemented sigmoid
plt.plot([0,10], [0.5, 0.5], linestyle = "dotted", color = "black")
plt.title("Sigmoid function")
plt.xlabel("x")
plt.ylabel("y")
plt.show()

作者图片
我们可以看到大多数值非常接近 0 或 1。在此范围内,如果预测的概率大于 0.5,那么它将被指定为 1,或者如果它小于 0.5,那么它将被指定为 0。
其中,传统的线性回归模型表现为:

作者图片
当 B 值代表校准模型的参数时,逻辑回归函数变为:

作者图片
其中 P(x)可被视为给定输入的输出等于 1 的概率(给定输入的输出等于 0 的概率由 1- p(x)给出)。然后,模型拟合具有相同的目的,即确定系数(B)的最佳值,使得曲线和每个 y 的数据点之间的距离尽可能接近。
探索数据
我们使用的数据集来自 UCI 机器学习库,专注于能够基于几个变量预测一个人是否患有糖尿病。在此范围内,我们有八个预测值和一个结果:
- 怀孕次数=既往怀孕次数
- 葡萄糖=葡萄糖耐量试验中的血糖水平
- bp =舒张压值(毫米汞柱)
- skin_thickness =三头肌皮褶厚度(mm)
- 胰岛素=胰岛素水平
- bmi =身体质量指数(bmi),𝑤𝑒𝑖𝑔ℎ𝑡ℎ𝑒𝑖𝑔ℎ𝑡2=𝑘𝑔𝑚2weightheight2=kgm2
- 谱系=糖尿病谱系功能
- 年龄=年龄(岁)
- 结果=健康结果(1 =糖尿病,0 =健康)
我们不一定需要理解这些与糖尿病有什么关系(如果有的话),因为有人在数据收集中已经为我们想到了这一点。然而,我们的任务是创建探索这些关系的模型,并对它们进行评估。
模型拟合
创建一个模型的主要目的之一是确保它可以推广到看不见的数据,这样它就不会过度拟合它训练的数据(偏差与方差权衡)。这意味着,我们在建模之前要做的第一件事是拆分训练和测试数据集中的数据,以便我们可以保存一些数据,从而能够准确地评估模型的性能。我们可以这样做:
# split data into features/inputs and targets/outputs
feature_cols = ['pregnancies', 'insulin', 'bmi',
'age', 'glucose', 'bp', 'pedigree']
X = diabetes[feature_cols] # features
y = diabetes.outcome # target variable# split data into training and validation datasets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
既然我们已经将数据放入了训练和测试数据集,我们就可以创建将要使用的模型了。为此,我们导入必要的库和模型,然后将模型实例化为:
from sklearn.linear_model import LogisticRegression# instantiate the model
model = LogisticRegression()
此时,我们已经创建了一个LogisticRegression对象的实例。然而,在此过程中,我们使用了默认的参数设置,这将有助于确定哪些参数可能是我们感兴趣的,以及它们的含义:
fit_intercept-布尔值,决定是否计算截距𝛽0β0(为真时,默认)或将其视为等于零(为假时)。intercept_scaling-浮点数,定义了截距𝛽0β0 的缩放比例,(默认为 1.0)。class_weight-字典,“平衡”或None(默认),定义与每个类别相关的权重。当None时,所有类的权重都为 1。solver-字符串,用于拟合模型的解算器。默认为“liblinear ”;其他选项包括“牛顿-cg”、“lbfgs”、“sag”和“saga”。tol-浮点数,定义停止程序的公差(默认为 0.0001)。n_jobs-整数或None(默认),定义要使用的并行进程的数量。None通常表示使用一个内核,而-1 表示使用所有可用的内核。
在更复杂的应用程序中还可以探索更多。
考虑到这一点,我们可以使用默认值,并将其与我们现有的训练数据进行拟合,如下所示:
# fitting the model
model.fit(X_train, y_train)
通过从模型中提取截距和斜率,我们可以看到它的表现:
coefficents = {"Features": ["Intercept"] + feature_cols,
"Coefficients":np.concatenate((model.intercept_ ,model.coef_[0]))}coefficents = pd.DataFrame(coefficents)coefficents

作者图片
我们可以将这些解释为积极的迹象,即在其他条件相同的情况下,人数越多,他们患糖尿病的可能性越大,人数越少,他们患糖尿病的可能性越小。尽管这些影响的实际大小可能难以解释,并且取决于所涉及的变量的范围和大小。理解这一点的一个很好的来源是 this 链接。
评估绩效
一旦我们拟合了模型并检查了参数,我们就可以看到它的表现以及在看不见的数据上的表现。虽然在一个好的模型中,许多预测都是准确的,但不可避免地会有误差。
在我们的应用中,有时模型会将健康个体误分类为糖尿病个体(假阳性),将糖尿病个体误分类为健康个体(假阴性)。然而,为了全面评估性能,我们可以使用各种不同的方法来理解和评估模型的性能。其中包括:

作者图片
选择使用哪种指标将取决于您的目标,因为不同的模型实现可能会以牺牲一种指标的性能为代价来提高另一种指标的性能。
我们可以做的第一件事是对我们设置的测试数据集的看不见的数据做一些预测。为此,我们可以使用模型中的.predict()方法来检查我们的测试数据集,如下所示:
y_pred = model.predict(X_test)
y_pred[0:5]#out:
array([1, 0, 0, 1, 0], dtype=int64)
我们可以看到,模型将个人分为 1 类或 0 类(是否患有糖尿病)。因为我们知道这些个体实际上是否患有糖尿病,所以我们可以使用这些知识来评估模型性能。
我们可以从分别检查准确度分数、精确度分数和召回分数开始:
# metrics
print("Accuracy for test set is {}.".format(round(metrics.accuracy_score(y_test, y_pred), 4)))
print("Precision for test set is {}.".format(round(metrics.precision_score(y_test, y_pred), 4)))
print("Recall for test set is {}.".format(round(metrics.recall_score(y_test, y_pred), 4)))#out:
Accuracy for test set is 0.8073.
Precision for test set is 0.766.
Recall for test set is 0.5806.
或者,我们也可以使用 scikit learn 的内置功能获得分类报告,例如:
print(metrics.classification_report(y_test, y_pred))

作者图片
从中我们可以看出,虽然我们在模型中有很好的整体准确性和精确度,但在召回率方面表现很差。这告诉我们,虽然该模型在真阳性方面表现良好,但在假阴性方面有点困难。
我们可以通过一个混淆矩阵来实现这一点,该矩阵可以在一行代码中提取,然后绘制如下:
#confusion matrix
conf_mat = metrics.confusion_matrix(y_test, y_pred)# plotting the confusion matrix
plt.figure(figsize=(12,6))
plt.title("Confusion Matrix")
sns.heatmap(conf_mat, annot=True, fmt='d', cmap='Blues')
plt.ylabel("Actual Values")
plt.xlabel("Predicted Values")
plt.savefig('confusion_matrix.png')

作者图片
我们可以直观地看到分类报告如何转化为实际结果。
我们可以为此使用的另一种评估技术是接收器操作特性(ROC)曲线,这在许多机器学习上下文中都可以看到。这用于绘制真阳性率与假阳性率,显示灵敏度和特异性之间的权衡。在该图中,理想曲线是紫线,而完全随机模型的结果是红线,目标是尽可能接近紫线。
# ROC curve
y_pred_proba = model.predict_proba(X_test)[::,1]
fpr, tpr, _ = metrics.roc_curve(y_test, y_pred_proba)
auc = metrics.roc_auc_score(y_test, y_pred_proba)
plt.plot(fpr, tpr, label="auc = " + str(round(auc,2)))
plt.plot(x, y, color = "violet", label = "Ideal")
plt.plot([0,1], [0,1], color = "red", linestyle = "--",
label = "random")
plt.legend(loc=4)
plt.show()

作者图片
这种性能通过曲线下面积(AUC)来量化,曲线下面积显示模型与理想性能的接近程度。理想模型的“最佳”AUC 分数应该是 1,而我们的随机模型的 AUC 是 0.5。因此,我们可以看到我们的模型虽然不完美,但表现相对较好。
结束语
我们已经学习了如何实现一个简单的逻辑回归方法。这是一种相对直观、有效和优雅的分类技术,它为我们提供了给定一些观察结果的给定结果的概率。这使得它成为一种非常流行的分类方法。
这种方法的好处包括:
- 不需要很高的计算能力
- 易于实施(甚至从头开始)
- 易于解释和评估
然而,这样做的负面影响是:
- 它不能处理具有许多预测变量的高度复杂的模型
- 需要在大样本上训练
- 倾向于过度拟合
- 无法处理非线性问题
如果您想了解我们协会的更多信息,请随时关注我们的社交网站:
https://www.facebook.com/ucldata
insta gram:【https://www.instagram.com/ucl.datasci/
领英:【https://www.linkedin.com/company/ucldata/
如果你想了解 UCL 数据科学协会和其他优秀作者的最新信息,请使用我下面的推荐代码注册 medium。
https://philip-wilkinson.medium.com/membership
或者看看我写的其他故事:
米托简介:也能生成 Python 代码的数据科学家电子表格
类似于电子表格,但速度快 10 倍

动机
电子表格已经使用了很多年,并且有不同的原因,包括数据分析。他们的界面为用户提供了一定的舒适感。但是,随着要分析的数据量的增加,电子表格往往会因为速度慢而失去作用,这可能会令人沮丧且耗时。
这里出现了**Mito**,一个试图通过结合电子表格的优势和执行速度来弥合这一差距的工具,以改善用户体验。
在这篇文章中,我们将了解什么是mito,并带您了解它提供的一些特性。
什么是米托?
mito是一款开源的数据分析工具,由 Jacob Diamond-Reivich 开发。它本质上类似于 excel 电子表格,但用于将 python 分析速度提高 10 倍。使用mito,您可以在mito提供的数据分析接口mitosheet中轻松浏览您的数据集。
除了前面的好处之外,自动获取与在界面上执行的编辑相对应的 python 源代码,并在您自己的机器上导出您的最终分析结果,这不是很好吗?这些是mito!的一些好处
让我们从需求开始把事情弄清楚,包括从您的终端使用下面的 pip 指令安装mitosheet包
python -m pip install mitoinstaller
python -m mitoinstaller install
或者从你的 Jupyterlab 笔记本。
!pip install mitosheet
注意:确保重启你的内核,这样所有的修改都会生效。
安装完成后,您应该能够使用以下语法导入库。
# Import the library
import mitosheet
开始
我们将使用 Kaggle 上提供的 免许可 的成人收入数据集举例说明mitosheet的用法。
现在所有的要求都满足了,我们准备骑在巨人的肩膀上了!整个神奇的事情发生在我们可以用这个简单的命令得到的用户界面上
# Show the User Interface
mitosheet.sheet()
根据前面的说明,我们得到以下界面,用户可以从该界面轻松导入其文件,该文件可以是 CSV 或 Excel 文件。
我们可以注意到所有的标签页都是不言自明的,例如,撤销用于撤销一个动作,重做,与前一个相反。清除删除我们执行的所有操作,导入,导入数据集,导出导出数据集,等等。

Mitosheet 主界面(图片来自作者)
获取数据
我们可以使用左下角的 + 符号或顶部的导入选项卡来导入数据。

数据导入步骤(图片由作者提供)
在前面的步骤中选择了要导入的数据( + sign 和Import adults _ income _ data . CSV)后,我们得到了下面的界面,显示了数据,各列名称下的各列类型。例如,工作类是一个字符串( str )。在同一界面的右下方,我们可以看到行数(32 561)和列数(15)。

数据导入后的默认界面(图片由作者提供)
在每个过程的最后,生成相应的 python 代码和注释,就像上面的数据导入一样(下面的代码相同)。
美图 _ 数据 _ 导入. py
开始分析
Mitosheet提出与数据分析师日常活动相关的不同分析可能性。其中一些分析包括生成汇总统计数据、合并数据集、列重复数据删除、删除等。你可以在这里找到可能性的详尽列表。
在本节中,我们将主要关注前两种可能性,使用我们在入门一节中介绍的数据集来说明每一种可能性。在分析结束时,最终数据集将以不同的名称保存和导出。
汇总统计
默认情况下,Mitosheet显示列中值的频率直方图,以及 pandas 的[describe()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.describe.html)函数的输出,以获得更高级的统计信息。
获取统计数据的一般语法分为三个主要步骤:选择列>过滤器图标>汇总统计数据。我们可以对年龄列应用相同的方法,如下所示。

获取年龄列统计数据的过程(图片由作者提供)
选择汇总统计按钮后,在表格右侧自动生成如下结果。由于在截图上看不到这些信息,我决定在下面显示完整的统计信息:年龄分布和相应的汇总统计。对于数据集中的所有连续特征,可以产生类似的结果。

“年龄”列的汇总统计结果(图片由作者提供)
以下结果是我们从教育列得到的,教育列是一个分类列。此外,对于数据集中的所有分类特征,也可以发现类似的结果。

教育专栏的汇总统计结果(图片由作者提供)
合并数据集
mitosheet的**MERGE**模块用于将不同的数据集合并在一起。该功能提供了五种主要使用的合并可能性,如下所示。
- 查找合并 :它包括第一张表上的所有观察值,只匹配第二张表上的行。在第二个工作表中有多个匹配的情况下,只考虑第一个。
- 内连接 :只包含两张表都匹配的行。
- 外连接 :这一个包括两个表中的所有行,不管另一个表中是否有匹配。
- 左连接 :包含第一张工作表中的所有行,只匹配第二张工作表中的行。
- 右连接 :与前一个相反,它包括第二个表中的所有行,并且只匹配第一个表中的行。
例如,我从原始数据集创建了一个名为成人标识符. csv 的新数据集(只有年龄、教育、性别和 native.country 列和 3000 个随机观察)。使用基于 native.country 列的查找合并,将第二个数据集与第一个数据集合并。该过程可使用mitosheet执行,如下所示:
从第一节
1。类似于第一个数据导入第二个数据。
2 。选择合并选项卡,这将自动创建一个新的数据集( df3 )作为合并的结果。第二部分也被自动显示,以允许用户实现合并逻辑。
出自第一节
本节实现了合并两个数据集的逻辑。它要求用户选择与前面解释的五种可能性之一相对应的合并类型。然后,用户可以选择第一个表、第二个表和两个表中要保留的列。

合并界面(图片由作者提供)
完成合并逻辑后,通过点击右侧的 X 符号关闭合片部分,自动生成 df3 的内容。下面的 Python 代码也是在流程结束时生成的。
mito_merge.py
重命名最终数据并导出
当我们完成并满意我们的最终分析时,我们可能想要导出结果以进行进一步的操作。由于 df3 不是一个不言自明的名字,我们可以如下所示更改这个名字( final_adult_income )。

更改最终数据名称(图片由作者提供)
之后,我们可以将数据导出为 CSV 或 excel 文件,如下所示。
- 选择文件并选择顶部的
**EXPORT**选项卡,这将自动打开右侧的下载部分。 - 从这里,我们可以选择文件的格式(在本例中为 CSV ,然后选择下载 CSV 文件以结束该过程。

CSV 格式最终数据的导出过程(图片由作者提供)
结论
恭喜你!🎉 🍾您刚刚学习了如何使用mitosheet加快数据分析过程!请随意尝试,让您的数据分析更上一层楼。另外,如果你喜欢读这篇文章,点击关注按钮获取文章更新!
欢迎在 LinkedIn 上加我,或者在 YouTube 和 Twitter 上关注我。讨论人工智能,人工智能,数据科学,自然语言处理的东西总是令人愉快的!
再见🏃🏾
生产中的 ML 简介
原文:https://towardsdatascience.com/introduction-to-ml-in-production-834d57c406d
挖掘机器学习周期:范围、数据、建模和部署

机器学习操作,被称为 MLOps,是一个新兴的学科,它包括一组在生产中维护和部署机器学习模型的实践,以便它们是有效和可靠的。
这个词是“机器学习”和 DevOps 的持续开发实践的复合,devo PS 也是一套缩短系统开发生命周期和提供高质量软件持续交付的实践。
MLOps 覆盖了整个 ML 生命周期,分为四个部分:范围、数据、建模和部署。

图一。ML 项目生命周期。 Ref :图片作者。
本文讲解的很多概念都来自 Coursera 课程“生产中的机器学习简介”,强烈推荐观看。
1.辖域
范围界定是我们计划项目并决定完成什么和如何完成的过程。在此阶段,必须定义准确性或延迟等关键指标,以及资源和时间表。
一般来说,这个阶段涵盖了以下过程:(1)确定一个业务问题,(2)思考潜在的 AI 解决方案,(3)评估潜在解决方案的可行性和价值,(4)确定项目的时间表和预算。
同样,在这一阶段,有必要根据既定的时间表和预算对项目的技术可行性进行初步评估。为此,我们可以将技术和商业团队召集在一起,就定义的条件达成一致。如果团队不确定规格,进行概念验证(POC)来测试可行性可能是个好主意。
2.数据
一旦我们定义了项目的范围,下一个阶段就是为项目收集适当的数据,对其进行适当的标记,并设置用于建模阶段的基线。
2.1.标记
关于数据,有两种主要类型的问题:
- 小数据(≤10K)与大数据(> 10K)
- 结构化数据与非结构化数据
对于小数据,标签至关重要,而对于大数据,主要挑战在于数据处理,即如何收集、过滤、排序、处理、分析、存储数据,然后以可读格式呈现。此外,虽然人类或数据增强技术可用于标记非结构化数据,但通常难以获得结构化数据集的新数据。在为项目选择团队时,一个好的做法是选择处理过相同类型数据的人。
要获取标签,建议与客户讨论如何在范围界定阶段获取数据标签。理想情况下,客户提供标签。如果不是这样,有趣的是他们也参与了标记过程,因为他们通常拥有领域知识。
如果没有标签,那么就需要决定标签的工作量。例如,如果你计划在训练阶段花 3 天时间,在错误分析阶段花 2 天时间,那么你可能需要花 1-2 天时间来标记数据。否则,与项目的其他部分相比,贴标成本会大幅增加。如果要标注的数据很多,不如先标注一部分数据,训练模型并评估误差,然后再标注,迭代重复这个过程。
最后,一个好的做法是花一些时间手动检查标签:( 1)了解数据和标签,以及(2)确保大多数标签都被正确标记。
2.2.基线定义
在这个阶段,建立一个基线总是一个好主意。当基本事实标签已经被外部定义时,人的水平性能(HLP)给出贝叶斯误差/不可约误差的估计。
关于 HLP,低准确度可能表示不明确的标签说明。当几个贴标签机对同一样品贴标签时,可能会观察到这种情况。提高标签一致性不仅会提高 HLP,还会提高模型性能,这是项目的最终目标。
除了从 HLP 获得基线,其他选择是获得文献中提出的类似解决方案的基线,或者获得由旧模型建立的基线。
3.建模
第三阶段是建模,这是一个介于训练阶段、错误分析和性能审计之间的迭代过程。
3.1.培养
这个阶段包括模型的训练,以获得尽可能高的精度。有两种可能的方法:
- 以模型为中心的视图:专注于为可用数据构建最佳模型。这种方法侧重于保持数据固定,并迭代地改进代码/模型。
- 以数据为中心的模型:它专注于使用工具来提高数据质量。这种方法侧重于保持代码固定,并迭代地改进数据。
一般来说,在实际应用中最好使用第二种方法。换句话说,最好将训练集中在好数据上,而不是大数据上,因为拥有好数据的合理算法往往会胜过拥有不太好数据的伟大算法。
要开始建模,首先要搜索文献,看看什么是可能的,并找到开源实现(如果它们可用的话)。此外,在训练大型数据集之前,尝试对小型训练数据集进行过度拟合是一个很好的做法。最后,实施一个好的跟踪方法(使用的代码、超参数、结果……)来更清楚地了解所进行的实验,以及如何得到最精确的模型。
3.2.误差分析
一旦您训练了模型,通过将我们的模型性能与已建立的基线进行比较,确定您是应该用当前的数据投入更多的精力来训练模型,还是应该进入部署阶段。
错误分析会告诉你每个类别是否有改进的空间。通过此分析,您可以确定是否有必要收集更多数据、提高标注的准确性或使用数据扩充来提高未达到基线的类的准确性。
3.3.绩效审计
一旦你结束了错误分析,检查准确性,公平性和偏见。错误分析、用户反馈和竞争对手的基准可以为添加新功能提供灵感。
此外,考虑当前的结果,并头脑风暴系统在部署阶段可能出错的方式。此外,建立度量标准来评估部署阶段的性能,以确保模型在生产中的某些数据没有偏差。在这个阶段,尽可能与业务或产品所有者保持一致,以包含尽可能多的领域知识。
4.部署
部署阶段是机器学习生命周期的最后一步,将最终产品交付给客户。在这一阶段,两个主要挑战是:
- 概念漂移(目标变量的统计特性随时间变化)和数据漂移(目标变量的统计特性随时间变化)。
- 软件工程问题,如实时与批处理、云与浏览器、计算资源(CPU/GPU/内存)、最大允许延迟或安全性和隐私。
与建模阶段相同,部署也是模型部署、监控和系统维护之间的一个迭代过程。
4.1.模型部署
常见的部署案例有:
- 有新产品/新功能
- 有一个新系统取代了旧的 ML 系统
- 有一个系统可以自动完成/帮助完成手工任务
此外,根据部署情况,有不同类型的部署:
- 影子模式(Shadow mode):新数据通过一个新部署的模型运行,而该模型实际上没有向客户返回预测的技术。
- 金丝雀部署(Canary deployment):向用户子集逐步发布应用或服务
- 蓝绿色部署:利用两个相同环境的部署策略,一个蓝色和一个绿色环境采用不同型号。这通常在旧模型已经在生产中运行时使用。
一般来说,最好是(1)有一个简单的方法回滚到旧系统,和(2)部署系统,逐步升级和监控。
最后,当使用任何提到的技术部署模型时,这个模型不一定需要替换人。事实上,有不同程度的自动化,如下图所示。

图二。自动化程度。参考:图片由作者提供。
4.2.监控和系统维护
这需要监控您的 ML 模型的变化,如数据漂移、概念漂移和模型退化,并确保模型保持可接受的性能水平。重要的是,不仅要监控将向客户端显示的输出指标,还要监控输入指标(缺失值的数量、特性的分布等)和软件指标(内存、延迟、吞吐量、服务器负载等)。要构建监控系统,您可以与团队一起集思广益,找出可能会出错的地方,以及一些可以发现问题的统计数据/指标/KPI。
4.3.系统维护
从监控系统中,我们可以发现与需要解决的概念漂移或数据漂移相关的新问题。根据这些见解,我们可以使用新数据手动或自动重新训练模型,以提高准确性。实际上,虽然用户数据漂移较慢,但企业对企业(B2B)应用程序往往漂移较快。
结论
如今,围绕机器学习项目开始出现两个分化良好的群体。一方面,那些在研究领域工作的人试图给 ML 社区带来新的和创新的模型。另一方面,那些从事实际应用机器学习项目的人主要使用文献中找到的模型,然后将它们投入生产。对于后者,我认为了解完整的机器学习生命周期并至少了解其中一个部分是成为一名成功的专业人士所必需的。
如果你喜欢这篇文章,请考虑 订阅 。你将获得我所有的内容+所有其他来自牛逼创作者的文章!
Python 中 NACA 翼型空气动力学简介
原文:https://towardsdatascience.com/introduction-to-naca-airfoil-aerodynamics-in-python-72a1c3ee46b1
用 Python 理解 NACA 4 系列翼型
介绍
这篇文章旨在解释 NACA 翼型的基本特性,特别是与空气动力学入门学生相关。首先讨论的是翼型几何背后的基本理论。然后,这些方程的 Python 实现使用 Matplotlib 计算用于绘制 NACA 4 系列 2D 翼型的相关数字属性。

Jerry Zhang 在 Unsplash 上的照片
翼型是机翼的横截面。国家航空咨询委员会 (NACA)开发并测试了一系列翼型,称为 NACA 翼型。图 1 显示了一系列这样的样品机翼剖面。
四位数和五位数系列是初学空气动力学课程最常学的,但也有六位数的型号。本文重点了解四位数系列,比如一个 NACA 4415 翼型。

图 1-NACA 翼型系列-来源:兰利研究中心
翼型几何形状
图 2 是一个示例对称翼型,其中概述了关键几何参数。
- 前缘和后缘:分别为机翼的最前和最后点
- 弦 : 连接机翼前缘和后缘的直线
- x : 前缘从零开始沿弦的水平距离
- y : 相对于水平 x 轴的垂直高度

图 2-对称翼型几何形状(图片由作者提供)
图 3 描绘了一个弧形翼型。曲面本质上类似于曲率。
- 中弧线:位于上和下表面的中间,与几何中心线同义。
- 厚度(t) :沿翼型长度的高度分布

图 3——弯曲翼型几何形状(图片由作者提供)
从图中可以明显看出,两个主要变量表达了翼型表面的几何轮廓、弯度和厚度。
设计的一个关键方面是 4 系列翼型形状源自描述平均弧线和截面厚度分布的解析方程。后来的系列,如 6 系列,是使用复杂的理论方法衍生出来的。
4 系列方程
NACA 4415 是 4 系列家族中的一员。整数 4415 描述了 2D 的轮廓。
等式 1 对应于数字 1 ,以弦的百分比给出最大拱度(m) 。因此,4415 的最大弯度是弦长的 4%。

等式 1——最大弯度占弦的百分比(图片由作者提供)
数字 2 被输入到方程 2 中,方程 2 提供了到前缘的最大弯度(p)距离,单位为十分之一弦。因此,4415 翼型的最大弯度点出现在沿弦长的 40%处。

方程 2-D最大弯度与前缘的距离,单位为十分之一弦(图片由作者提供)
等式 3 利用了数字 3 和以及和4;他们给出了翼型的最大厚度(t) 占弦长的百分比。因此,4415 翼型厚度为弦长的 15%。

等式 3——弦的最大厚度百分比(图片由作者提供)
要点 1 给出了 Python 代码,该代码定义了基于 NACA 四位数提取数值 翼型特性的三个函数。
对于对称翼型,前两位数为零。即 0015 → m = 0,p = 0 。
要点 NACA 翼型数字的参数转换
根据 x 坐标是小于还是大于最大外倾角( p )的位置,两个等式指定了平均外倾角线,如等式 4 所示。

等式 4 —平均拱线(图片由作者提供)
重要的是要重申,所提出的方程是纯分析性的,是 NACA 通过研究和实验设计的。
Gist 2 提供了等式 4 的 Python 实现。
要点 2——平均弧线 Y 坐标
yₜ 对应厚度分布厚度分布。中弧线以上(+)和以下(-)的厚度值取决于等式 4。

等式 5——平均拱线周围的厚度分布(图片由作者提供)
x⁴ 系数根据后缘是打开还是关闭而变化。即,-0.1036 对应封闭曲面,对于有限厚度后缘,用 -0.1015 代替-0.1036。
yₜ 是使用 Gist 3 在 Python 中计算出来的。
要点 3——计算翼型厚度分布的解析函数
厚度值垂直于中弧线加上。因此,需要一个角度来指示加法的偏移。**
对中弧线求导得到曲线切线的斜率。 D 将方程式 4 相对于 x 微分得到方程式 6。

等式 6——中弧线的导数(图片由作者提供)
使用要点 4 中的 Python 代码找到中弧线的导数。****
要点 4——中弧线的导数
计算该导数的反正切给出了从垂直方向偏移厚度的角度 θ。回头参考图 3,了解 θ 的作用。

等式 7 —厚度偏移角度(图片由作者提供)
最后,为了计算上和下翼型表面** 坐标,使用等式 8-11,其中 θ 等于 MCL 在 x 处导数的反正切,使用等式 7 确定。**
Gist 6 提供了计算翼型上下表面最终(x,y)值的代码。

方程式 8-11——翼型表面的最终坐标(图片由作者提供)
要点 6——翼型表面的最终(x,y)坐标
绘图结果
使用( xᵤ,yᵤ )和( xₗ,yₗ )的结果值绘制最终翼型。图 4 描述了使用 Matplotlib 绘制的 NACA 4415。

图 4-NACA 4415 翼型图
图 5 显示了另一个弧形 4 系列翼型的样本,NACA 2412 。直观地比较 4415 和 2412 的几何特性,查看差异,注意 y 轴刻度。

图 5-NACA 2412 翼型图
如上所述,这些解析表达式对对称翼型有效。
中弧线和厚度分布都与弦完美对齐,从图 6 中的 0015 中可以清楚地看到。

图 6-NACA 0015 对称翼型图
结论
每个等式都是通用的,可以用任意四个整数进行参数化,以可视化 4 系列 NACA 家族的任何成员。
本文概述了基本的翼型特性,并演示了一种实现几何表达式来绘制机翼的 2D 表面轮廓的方法。
感谢阅读。请告诉我你是否对其他与空气动力学相关的文章感兴趣。
**</5-python-projects-for-engineering-students-8e951b7c131f> https://medium.com/@andrewdaviesul/membership
参考
【1】空气动力学基础。第六版。小约翰·安德森空气动力学馆长。国家航空航天博物馆。史密森学会。
【2】NACA 翼型 — NASA。最后更新:2017 年 8 月 7 日编辑:Bob Allen
【3】NACA 翼型系列(aa 200 _ Course _ Material)——斯坦福【4】讲解:NACA 四位数翼型【飞机】——工程师乔希**
NoSQL 图形数据库简介
原文:https://towardsdatascience.com/introduction-to-nosql-graph-databases-fb2feac7a36
图形数据库类型、结构和属性概述

香农·波特在 Unsplash 上的照片
介绍
什么是 NoSQL 数据库?
NoSQL 代表“不仅仅是 SQL”NoSQL 数据库提供了一种不同于关系表的存储数据的替代方法。
NoSQL 数据库提供了存储结构化、半结构化和非结构化数据的灵活性。当您需要存储大量数据、根据不断变化的需求快速迭代以及向外扩展时,它们非常适合使用。
有多种类型的 NoSQL 数据库。4 最常见的 NoSQL 数据库是:
- 键值数据库:键值存储;类似于 Python 字典。通过使用键或搜索整个数据库进行查询。键值存储倾向于在内存中使用,并在其背后使用后备存储。
- 文档数据库:文档的集合,其中每个文档都是 JSON 或类似 JSON 的格式。每个文档都包含成对的字段和值。主存储位于存储层,我们将其缓存到内存中。
- 宽列数据库:类似于关系数据库表;区别在于后端的存储不同。我们可以将 SQL 放在宽列数据库之上,这使得它非常类似于查询关系数据库。
- 图形数据库:将数据存储为节点(顶点)和关系(边)。顶点通常存储对象信息,而边表示节点之间的关系。我们可以在图形数据库中使用类似 SQL 的查询语言。
这篇文章将重点提供一个关于 NoSQL 图形数据库的概述。
图形数据库的好处
NoSQL 图具有节点(顶点)和关系(边),允许我们对各种场景进行建模——从火车路径系统到绘制社交媒体连接,到设备网络,等等。
我们可以给节点分配标签,并根据这些标签对节点进行分类。我们还可以分配键值对形式的属性(权重)。关系也可以有标签和属性,但也可以有方向。方向为关系提供了意义;它们可能是无方向的、单向的或双向的。
随着数据量的增加、快节奏的敏捷迭代以及向外扩展的需求,图数据库在满足这些需求方面发挥着关键作用。
- 即使数据随时间增长,图形数据库的性能也保持不变
- 图形数据库查询输出实时结果
- 他们可以对大数据执行实时更新,同时支持查询。
- 图形数据库提供了快速适应不断变化的需求的灵活性。需求变化的快速迭代需要能够在不危及当前功能的情况下对现有的图结构进行修改。
图形类型和结构
了解不同的图形类型和结构很重要,包括形状、特征和密度。
图表形状
我们将涉及的主要形状是随机的、小世界的和无尺度的。
随机:没有图案的扁平状。所有节点相互连接的概率相同。
- 例如:社会安全号码、出生/死亡、退休
小世界:这个形状有高度的局部聚集,导致平均路径长度较短。没有一个节点的距离超过几个关系。一个可以思考的类比是当你听到“嘿,世界真小!”
- 例如:LinkedIn——你遇到的大多数人都是二级或三级关系
无标度:多标度的“轴辐式”。这符合幂律分布——一个量的变化导致另一个量相对成比例的变化。
- 示例:一组相互关联的邮政编码组成一个县
图表属性
连接&断开:连接是两个节点之间的路径,不考虑距离。您在图连接中可能观察到的一个问题是,在大多数图算法中可能不会分析断开的节点。类似地,如果您的图包含一个与主图断开连接的连接节点岛,也会出现问题。

作者照片
有向&无向:有向图是指图关系(边)有方向。该方向通过指定源节点和目的节点来进一步定义节点关系。如果一个图形算法需要方向,无向图是不合适的。

作者照片
加权&未加权:权重是置于关系上的数值。图中的权重可以是有向的,也可以是无向的。如果图算法需要权重,则不包括未加权的关系。

作者照片
非循环&循环:循环图是指图中有从一个节点回到自身的循环或路径。许多常见的图算法需要非循环图;周期会导致这些算法停滞不前,永远重复下去。

作者照片
树&生成树:一棵树是一个无环图,可以是有向的,也可以是无向的。生成树是一种树,其中所有节点都在图中,并且移除关系以移除循环。由于在一个循环中有多个移除关系的选项,所以在一个图中可以有多个生成树。
最小生成树是具有最小成本的生成树。如果图是加权的,那么你通过路径权重来计算成本。如果这个图是不加权的,那么你就可以找到到达每个节点的跳数最少的生成树。

作者照片
图形密度
边(关系)的数目与图所能包含的最大边数之比称为图密度。如果一个图有很多边,那么这个图被认为是更加稠密的。如果一个图没有很多边,它被认为是稀疏的。
图的最大密度是指图中的每个节点都与每个节点相连。如果我们知道节点的数量,我们就可以计算出最大密度。图表的最大密度和实际密度可以计算如下:


我们经常看到非常密集的图(即:分析网络流量或社交媒体)。我们在研究图形算法时面临的问题是,在高密度的情况下,我们需要识别和剥离图层。另一方面,当我们在处理高层次的稀疏关系时,我们想看看是否可以根据我们做出的推断来添加关系。
摘要
在这篇介绍性文章中,我们了解到 NoSQL 图数据库在处理不断增长的数据量、快节奏的敏捷迭代以及向外扩展的需求方面发挥着关键作用。我们研究了图形和结构的主要类型,以及图形形状、密度和特征,如连通性、方向、权重、循环/非循环和树。为了在工作中实现最佳的结构和算法,理解图的属性非常重要。
NumPy 数组简介
原文:https://towardsdatascience.com/introduction-to-numpy-arrays-c0a1082afadd
了解 NumPy 数组的基础知识

Pierre Bamin 在 Unsplash 上拍摄的照片
NumPy 数组是来自 Python 库 NumPy 的一个数据对象,用于存储一个数据类型的对象。由于它的编程比同类的 Python 数据对象更接近内存,它可以更有效地存储数据集,因此处理速度也更快。
什么是 NumPy?
Python 提供了多种数据结构,可用于存储数据,无需额外的库。然而,这些结构,比如 Python 列表,非常不适合数学运算。在处理大量数据时,逐个元素地添加两个数字列表会很快对性能产生不利影响。
由于这个原因, NumPy 被开发出来,因为它提供了快速有效地执行数值运算的可能性。尤其重要的是来自线性代数领域的计算,例如矩阵乘法。
如何定义 NumPy 数组
顾名思义,数组是 NumPy 库的一部分。所以必须先导入才能使用数组。然后简单地通过在方括号中插入元素来创建。这里元素的顺序起了作用:
数组的属性是什么?
数组是元素的集合,这些元素必须都是相同的数据类型。大多数数字存储在其中,但字符串也可以存储。只有在一个数组中不同数据类型的混合是不可能的。
为了描述数组的结构,有三个基本的属性,这三个属性经常被混淆:
- 维数:数组中的维数表示查询一个数组的特定元素需要多少个索引。每个维度都可以用来存储相关信息。例如,如果您想按时间分析公司的销售数字,您可以使用一个维度来分析不同日期的销售额,而使用另一个维度来分析一个月的销售额。
- Shape:Shape 指定数组包含的所有维度的大小。例如,如果你有一个三维数组,你将得到一个长度为 3 的元组。元组的第一个元素表示第一维度的元素数量,第二个元素表示第二维度的元素数量,依此类推。
- 大小:最后,数组的大小表示数组中总共存储了多少个数字或元素。具体来说,这是形状返回的单个元素的乘积。
我们现在定义一个二维 NumPy 数组,它在两个维度中都有三个元素:
多维数组是如何构造的?
在应用程序中,一个维度通常不足以完整地写出事实。NumPy 数组也适用于存储多维对象。表格就是这样一个例子,它由两个维度组成,即行和列。这也可以通过将行指定为列表的列表来相对容易地定义:
类似地,可以添加其他维度。例如,我们可以创建一个对象,该对象只包含前一个示例中的表两次。这样我们就得到一个三维物体:
如何从数组中获取单个元素?
由于 NumPy 数组在结构上与 Python 列表非常相似,所以数组也使用所谓的索引来访问元素就不足为奇了。这意味着您可以根据位置查询单个元素。计数从 0 开始向上。
同样,您也可以使用负索引,从而从后面遍历数组。然而,与正索引相反,它从-1 开始,代表数组的最后一个元素。-2 则相应地是数组倒数第二个元素的索引:
在多维数组中,单个索引不足以查询单个元素。通常,为了能够查询一个元素而不获取元素列表,必须为每个维度指定一个索引。在我们的多维数组中,我们查询第一维的第二个元素:
Python 列表和 NumPy 数组有什么区别?
在本文的这一点上,您可能会认为 NumPy 数组只是对 Python 列表的一种替代,后者甚至有一个缺点,即只能存储单一数据类型的数据,而列表也存储字符串和数字的混合。然而, NumPy 的开发者决定在数组中引入一个新的数据元素肯定是有原因的。
NumPy 数组相对于 Python 列表的主要优势是内存效率和相关的读写速度。在许多应用程序中,这可能并不重要,但是,当处理数百万甚至数十亿个元素时,它可以节省大量时间。因此,阵列通常用于开发高性能系统的复杂应用中。
这是你应该带走的东西
- NumPy 数组是 NumPy 库中的一个数据元素,可以存储多维数组的信息。
- 与 Python 列表相比,它只能存储相同数据类型的元素。然而,反过来,它的内存效率更高,因此功能也更强大。
- NumPy 数组的基本属性是维数、形状和大小。
随机森林算法简介
原文:https://towardsdatascience.com/introduction-to-random-forest-algorithm-fed4b8c8e848
算法是如何工作的,我们可以用它来做什么

杰里米·毕晓普在 Unsplash 上的照片

随机森林是由个体决策树组成的监督 机器学习算法。这种类型的模型被称为集合模型,因为独立模型的“集合”用于计算结果。
什么是决策树?
随机森林的基础由许多单独的决策树形成,即所谓的决策树。树由不同的决策层和分支组成,用于对数据进行分类。
决策树算法尝试将训练数据划分到不同的类中,以便类内的对象尽可能相似,而不同类的对象尽可能不同。这会产生多个决策级别和响应路径,如下例所示:

决策树示例|作者照片
这棵树有助于决定是否在户外进行运动,这取决于天气变量“天气”、“湿度”和“风力”。决策树将答案的分类可视化为“是”和“否”,并非常简单地阐明何时可以户外运动,何时不可以。你可以在我们关于决策树的帖子中找到详细的解释。
不幸的是,决策树很快就会过度适应。这意味着算法变得太习惯于训练数据,并记住了它。因此,它在新的、看不见的数据上表现很差。
在机器学习中,目标实际上总是训练一种算法,该算法从训练数据集中学习某些能力,然后可以将它们应用于新数据。由于这个原因,现在很少使用决策树,取而代之的是非常相似的随机森林。这是通过所谓的系综方法实现的,这将在下一节详细解释。
随机森林如何工作
随机森林由大量的这些决策树组成,它们作为所谓的集合一起工作。每个单独的决策树做出预测,例如分类结果,并且森林使用大多数决策树支持的结果作为整个集合的预测。为什么多个决策树比单个决策树好得多?
随机森林背后的秘密是所谓的群体智慧原理。基本思想是,许多人的决定总是比单个人或单个决策树的决定更好。这个概念最初是在连续集的估计中认识到的。
1906 年,一头公牛在一次交易会上被展示给 800 人。他们被要求在真正称重之前估计这头牛的重量。结果表明,800 个估计值的中间值与牛的实际重量只相差 1%左右。没有任何一个估计接近正确。因此,作为一个整体,观众比任何一个人都估计得更好。
这可以以完全相同的方式应用于随机森林。大量的决策树和它们的聚合预测将总是优于单个决策树。

随机森林合集|作者照片
然而,这只有在这些树彼此不相关并且因此单个树的错误被其他决策树补偿的情况下才是真实的。让我们回到集市上牛的重量的例子。
如果参与者彼此不一致,即不相关,则所有 800 个人的估计值的中值只有可能比每个人更好。然而,如果参与者在评估之前一起讨论,并因此相互影响,则多数人的智慧不再出现。
什么是装袋?
为了让随机森林产生好的结果,我们必须确保各个决策树彼此不相关。我们使用所谓的装袋法。它是集成算法中的一种方法,确保在数据集的不同子集上训练不同的模型。
决策树对它们的训练数据非常敏感。数据中的一个小变化已经可以导致明显不同的树结构。我们在装袋时利用了这一特性。因此,在训练数据集的样本上训练森林中的每棵树,这防止了这些树彼此相关。
即使采集了样本,每个树仍然在具有原始数据集长度的训练数据集上进行训练。这是通过替换丢失的值来完成的。假设我们的原始数据集是长度为 6 的列表[1,2,3,4,5,6]。一个可能的例子是[1,2,4,6],我们将其扩展为 2 和 6,这样我们再次得到一个长度为 6 的列表:[1,2,2,4,6,6]。装袋是从数据集中抽取一个样本并用样本中的元素将其“扩充”回原始大小的过程。
随机森林算法的应用领域
类似于决策树,随机森林模型用于分类任务和回归分析。这些技术在许多领域都有应用,如医药、电子商务和金融。具体应用例如:
- 预测股票价格
- 评估银行客户的信誉
- 根据医疗记录诊断疾病
- 根据购买历史预测消费者偏好
随机森林有什么好处?
在分类任务中使用随机森林有一些很好的理由。以下是最常见的几种:
- 更好的性能:正如我们在这一点上多次解释的那样,集成算法的性能平均要比单个模型的性能好。
- 较低的过拟合风险:决策树有很强的记忆训练数据集的倾向,即陷入过拟合。另一方面,不相关决策树的中值不容易受到影响,因此为新数据提供了更好的结果。
- 可重复的决策:虽然在随机森林中寻找结果比用单一决策树更令人困惑,但它的核心仍然是可以理解的。类似的算法,如神经网络,无法理解结果是如何得出的。
- 较低的计算能力:随机森林可以在今天的计算机上相对快速地训练,因为硬件要求没有其他机器学习模型那么高。
什么时候不应该使用随机森林?
尽管在许多用例中,随机森林是一个可以考虑的备选方案,但也有不适合它们的情况。
随机森林应该主要用于分类任务,在这种任务中,所有带有少量示例的类都出现在训练数据集中。然而,它们不适合预测新的类或值,例如,我们从线性回归或神经网络中知道它们。
虽然训练随机森林相对较快,但是单个分类需要相对较长的时间。因此,如果您有一个需要进行实时预测的用例,其他算法可能更适合。
如果训练数据集的填充非常不均匀,这意味着某些类只有很少的记录。装袋过程中的样品受到这种影响,进而对模型性能产生负面影响。
这是你应该带走的东西
- 随机森林是由个体决策树组成的监督机器学习算法。
- 它基于群体智慧的原则,即许多不相关组件的联合决策优于单个组件的决策。
- Bagging 用于确保决策树彼此不相关。
- 随机森林用于医药以及金融和银行部门。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,请不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**
*https://medium.com/illumination/intuitive-guide-to-artificial-neural-networks-5a2925ea3fa2 https://medium.com/nerd-for-tech/what-are-deepfakes-and-how-do-you-recognize-them-f9ab1a143456 https://medium.com/@niklas_lang/membership
最初发布于https://database camp . de*。**
正则表达式简介
原文:https://towardsdatascience.com/introduction-to-regular-expressions-3371c86f7
正则表达式|编程|模式匹配
一个实用的指南,让你很快就能流利地使用正则表达式

照片由 Unsplash 上的 Clément Hélardot 拍摄
正则表达式是定义某种形式的搜索模式的字符序列。
正则表达式是搜索和替换字符串中文本的非常强大的工具。它是一种在任何编程语言中查找、格式化和操作文本的方法。
它们允许你做一些非常高级的搜索和替换操作,如果手工执行的话,这些操作会非常耗时。
正则表达式就像程序的瑞士军刀;它们可以用于各种事情,从搜索(从文件系统到数据库)到编程。
你可能是一个经验丰富的程序员,也可能是一个完全的初学者。无论如何,正则表达式是编程语言中非常强大的一部分,但不幸的是,大多数人都忽略了这一点。
正则表达式是一种更精确地搜索(和准备)文本数据的方法。您还可以使用它们进行调试、转换、验证,或者换句话说,处理字符串。一些常见的用例包括从博客文章中捕获所有电话号码,用 x 替换它们,或者从 HTML 元素中去除所有特殊字符。
正则表达式的美妙之处在于,它们是当今几乎所有编程语言的一部分。
无论您是 IT 人员、SEO 人员、编程人员、执行数据库查询人员,还是探索数据进行研究的人员,regex 都可以为您节省大量时间!
诚然,regex 一开始很难理解,但是一旦你开始在日常工作中使用它,它会改变你的生活。这个博客将帮助你学习和掌握正则表达式。
正则表达式是如何工作的?
使用 regex 相对简单。几乎所有现存的编程语言都支持正则表达式匹配(要么是本地的,要么是通过一些库或包)。
第一步是确定要在字符串中匹配和搜索的模式。这可以是任何字符序列。
然后,您希望将该模式转换成正则表达式。在整个过程中,使用正则表达式验证工具来测试你的正则表达式可能是明智的(剧透一下,网上有几个免费的)。
一旦构建了正则表达式,就该为您正在使用的编程语言找到相应的正则表达式引擎了。在这一点上,如果您浏览一下它的文档,那将是帮了自己一个大忙。
然后,您可以传入正则表达式和要匹配正则表达式引擎的字符串。正则表达式引擎将计算字符串并返回结果(根据您使用的正则表达式引擎,这可以是一个布尔值或一个 int 或实际的字符串)。

使用 Regex。来源:大卫·法鲁吉亚
构建我们的第一个正则表达式
在开始构建我们的第一个正则表达式之前,让我们先来看一下与正则表达式相关的主要语法。
/ — delimiter (start and end of regex)? — match 0 or 1 time* - match 0 or more times+ — match 1 or more times[] — range of acceptable values{} — exactly n characters| — create different branches() — groupingi — case insensitive^ — anchor to the beginning of the string$ — anchor to the end of the string
让我们从一个用例开始。假设我们需要构建一个能够验证十六进制颜色的正则表达式。
第一步:确定我们需要匹配的模式
如你所知,一个十六进制的颜色通常以一个 # 开头,然后是另外 6 个字符。
这 6 个字符需要在 A 到 F 或 0 到 9 的范围内。
步骤 2:转换成正则表达式
我将使用令人惊叹的 regex101 网站 来验证和测试我们的 regex。
让我们首先列出所有可能的值:
/#[ABCDEF0123456789]

测试案例 1。来源:大卫·法鲁吉亚
我们可以看到我们的红色十六进制测试用例(#EB4034)通过了验证。但是如果我们仔细观察,我们会发现匹配(基于蓝色突出显示)仅限于前 2 个字符。
这是因为我们指定模式必须是#后跟[]中指定的任何其他字符。
为了解决这个问题,我们可以在正则表达式的末尾使用+操作符来重复[]中的内容 1 次或更多次。
此外,我们可以使用-运算符来指定范围,而不必指定整个字符列表(即 ABCDEF 变为 A-F,0123456789 变为 0–9)。
我们还可以使用?运算符(因为有时候,人们认为#是理所当然的)。
让我们再次尝试这些变化。


测试用例 2(左)和 3(右)。来源:大卫·法鲁吉亚
正如我们在上面的测试案例中看到的,我们现在高亮显示了整个字符串。
在这一点上,这看起来不错,不是吗?尝试我们知道无效的测试用例总是明智的。
如果我们试图验证一个格式不正确的十六进制字符串,比如 AA,会发生什么?

测试用例 4。来源:大卫·法鲁吉亚
我们还是能找到匹配的。让我们来解决这个问题。
我们肯定知道十六进制代码应该有 6 个字符。我们可以使用{}来指定我们想要重复的次数。所以让我们把+和{6}交换一下。


测试用例 5(左)和 6(右)。来源:大卫·法鲁吉亚
不错!当正确格式化的十六进制代码通过验证时(在测试用例 6 中),我们没有测试用例 5 的匹配项。
然而,以上又产生了另一个问题。存在只有 3 个字符的有效十六进制代码,例如#B63。对于当前的正则表达式,这个测试用例将会失败。
我们可以使用|操作符来指定 or 条件。
让我们使用下面的正则表达式,它匹配 6 个字符或 3 个字符。
/#?([A-F0-9]{6}|[A-F0-9]{3})

测试案例 7。来源:大卫·法鲁吉亚
瞧啊。我们找到匹配的了。
我们的下一个测试用例是不同大小写风格(即小写或大写)的十六进制代码。就目前情况来看,#B63 是一个有效的十六进制数,但是#b63 会失败。
有两种方法可以解决这个问题。我们可以将下限范围指定为可接受的值:
/#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})
或
我们可以使用模式修改器。我们可以使用字符 i 来强调这个正则表达式不区分大小写。
/#?([A-F0-9]{6}|[A-F0-9]{3})/i

测试用例 8。来源:大卫·法鲁吉亚
注意:我使用/ig 而不是/i 来一次测试多个字符串。/g 代表全球回报。
需要记住的一点是,正则表达式引擎将在整个字符串中搜索匹配项。
因此,如果我们传入这个字符串:test #B63,我们将得到一个不正确的匹配。

测试用例 9。来源:大卫·法鲁吉亚
为了确保完整的字符串匹配正则表达式,我们需要使用锚点。如上所述,^字符将锚定到字符串的开头,而$符号锚定到字符串的结尾。
/^#?([A-F0-9]{6}|[A-F0-9]{3})$/i

测试用例 10。来源:大卫·法鲁吉亚
不匹配。太好了!
在我们的正则表达式中加入一个很酷的技巧就是空格缩写。使用\s*字符,我们将告诉正则表达式引擎忽略任何空白。
/^\s*#?([A-F0-9]{6}|[A-F0-9]{3})\s*$/i

测试用例 11。来源:大卫·法鲁吉亚
现在我们有了。我们第一个完整的 regex 例子,能够验证十六进制值。
但是,在我们出发之前,还有一个很酷的技巧我们必须探索。
让我给你介绍一下 https://regexper.com/。这是一个神奇的工具,它以一种很酷的方式可视化你的正则表达式。当我们试图理解和调试正则表达式时,这个过程非常有用。

我们的十六进制代码正则表达式的可视化。资料来源:regexper.com
现在你知道了!你的第一个正则表达式,已经测试完成。这基本上是正则表达式的全部要点,你构建的每个正则表达式很可能遵循相同的过程和原则。然而,仍然有一些其他的高级语法规则和一些我们需要避免的常见陷阱。但是那些是改天的!
现在,练习为您的用例构建一些简单的正则表达式验证,不要害怕弄脏您的手!
你喜欢这篇文章吗?如果是,请考虑订阅我的电子邮件列表,以便在我发布新内容时得到通知。
https://david-farrugia.medium.com/subscribe
另外,考虑成为会员来支持我和你喜欢的其他作家。每月 5 美元,你就可以无限制地阅读 Medium 上的每一篇文章。
https://david-farrugia.medium.com/membership
想联系吗?
我很想听听你对这个话题的想法,或者其他什么。如果你想联系我,请给我发电子邮件到 davidfarrugia53@gmail.com。
强化学习介绍:时间差异,SARSA,Q-学习

菲利普·格利克曼在 Unsplash 上的照片
强化学习是机器学习中最复杂的领域之一,因为它的数学复杂性,以及它试图实现的雄心勃勃的任务。
简单地说,RL 算法的最终目标是使智能体能够在一个环境中行动,以最大化总回报。
说起来容易做起来难:这一句话隐藏了许多问题,例如:
- 我如何定义“目标”和“奖励”?
- 我如何让一个代理“行动”?
- 我如何对“环境”建模?
这个问题比看起来要困难得多,但迄今为止已经取得了很多进展,最著名的例子是科学突破,如 DeepMind 的 AlphaGo 和 AlphaTensor 。
然而,如果你想深入了解一个领域,你必须从基础开始。这篇文章的范围将是给出 RL 的高层次概述,以及介绍代表许多其他更高级算法的基础的三种算法:时间差异、SARSA 和 Q-Learning。
强化学习:构建模块
在 RL 中,我们利用一个简单的模型来描述代理如何与空间交互,这就是马尔可夫决策过程。
MDPs 代表了一种数学框架,用于对动态部分由代理控制,部分由环境特征引起的情况进行建模。在 MDP 中,代理人不断地与环境互动,环境以影响后续代理人行动的回报作为回应。我们来看看下面这张图:

一个简单的 MDP,作者的形象
在每个时间步 t ,代理接收环境的状态 S ₜ的表示,这决定了对动作 A ₜ.的选择在下一个时间步 t+1 ,代理收到一个奖励 Rₜ₊₁,告知它过去的行为有多好,以及一个新的环境表示。
在有限的 MDP 中,州 Sₜ、行动 Aₜ和奖励 Rₜ具有有限数量的元素,Sₜ和 Rₜ具有明确定义的离散概率分布,仅取决于前面的州和行动。这些特点使得问题通常更容易处理。然而,并不是所有的问题都可以用有限的状态、行动或奖励来建模,然而我们将做出的许多假设和结论很容易推广到连续的设置中。
代理和环境之间的持续交互产生了一个轨迹:
S₀、A₀、R₀、S₁、A₁、R₁、S₂、A₂、巴西……
从状态 S ₜ转换到 S ₜ₊₁的概率因此受到动作 A ₜ的选择的影响,但是,给定 S ₜ和 A ₜ,它有条件地独立于所有先前的状态和动作。因此,我们说 MDP 的状态转移满足马尔可夫性质。
代理人的最终目标是在长期内最大化总回报,这通常考虑到一个贴现因子γ ,计算如下:

贴现回报,等式来自1
折现因子γ决定了代理人有多渴望眼前的回报(γ = 0)或有多长远的眼光并以未来的高回报为目标(γ = 1)。
代理人的行为应该是为了转换到产生高回报的状态。代理的行为由策略 π(a|s) 决定,策略是一个输出执行动作 a 的概率的函数,假设代理处于状态 s 。在给定的策略下,我们可以定义一个状态-值函数(和一个状态-动作值函数),它测量代理从 s (并执行 a )开始并随后跟随 π 所获得的期望回报。

状态值函数和状态-动作值函数,方程来自1
RL 算法的目标通常有两个:
- 预测:估计给定策略的价值函数 π

策略的状态值函数和状态-动作值函数的估计值 π
2.控制:寻找最优策略 π* ,即长期收益最大化的策略。

最优策略的状态值函数和状态-动作值函数的估计值 π

最优贪婪策略,来自1的等式,通过选择产生最高 Q 值的动作获得
注意,这两个任务是内在相关的:如果一个代理遵循一个策略 π ,我的第一个目标是评估这个策略有多好(预测)。一旦我估计了策略的价值函数,我就可以决定如何改变它,以便获得更好的价值函数(控制)。
了解环境的模型,也就是了解行动和回报的概率分布,在解决这两个任务时会有很大的优势。尽管如此,该模型往往是隐藏的或过于复杂而难以处理,因此算法最好从经验(或集)中学习,这些经验是状态、动作和奖励的序列,以最终状态结束。例如,玩完整的井字游戏代表一个情节,当其中一个玩家赢了(或者两个玩家都和棋)时就达到了结束状态。

一个简单的井字游戏插曲,图片由作者提供
可用于解决预测和控制任务的算法列表很长,并且它们中的每一个都能够在某些假设下获得最佳结果。在这里,我决定介绍三个支柱算法,它们被证明总体上运行良好,但也代表了许多更复杂算法的基础:
- 时间差异
- 萨尔萨
- q 学习
时间差
时间差异被认为是强化学习的中心思想,因为它在没有环境模型的情况下从原始经验中学习。它解决了预测问题,即估计 MDP 状态的值函数。
从经验中学习的问题是,人们必须等待一集的结束(从而等待最后的回报)来更新价值函数的估计。TD 克服了这个障碍,只需等待一步就能完成。

最简单版本的 TD,α的更新规则是更新速率。来自1的方程
在每个时间步 t 处,状态 Sₜ 的价值函数基于立即接收到的奖励 Rₜ₊₁ 以及新状态 Sₜ₊₁ 的估计而被立即更新。这是 TD 的最简单版本,称为 TD(0),其中零表示必须等待一个单独的步骤来执行更新,但这一思想可以扩展到更复杂的算法,如 n 步 TD 和 TD(λ) 。正如您所注意到的,一个状态的更新规则利用了另一个状态的估计:这种方法被称为 bootstrapping 。

TD(0)的过程,来自1
我们提到了使用 TD 的好处,下面是一个总结:
- 它不需要一个环境模型
- 它的估计收敛(快速)到真实状态值函数
- 它执行在线增量更新,即它不需要等待剧集结束来更新价值函数的估计。
萨尔萨
我知道你在想什么:这么奇怪的算法名字。事实上,SARSA 的名字来自于从状态 S 到 S’的转换:

从状态 S 到 S '的轨迹,这就是 SARSA 算法的名称
SARSA 是一种基于策略的 TD 方法,用于解决控制问题,这意味着它试图为给定的任务找到最佳策略。为什么是“政策上”?
在 on-policy 学习中,使用当前策略𝜋(𝑎|𝑠).从所采取的行动中学习最优值函数

SARSA 的更新规则,等式来自1。注意,我们知道估计 Q(s,a),而不是 V(s)
事实上,SARSA 被定义为“按策略”的原因是我们利用当前策略来更新 Q(s,a) 的事实。如果这还不清楚,等着看 Q-Learning 做了什么,并检查区别。
那么,萨莎是如何学习最优策略的呢?我们提到它是一种 TD 方法,这意味着它利用 TD 更新规则来估计值函数。但是它如何改进策略以获得一个更大的值函数呢?
SARSA 通过以ε贪婪的方式改变策略 π 来实现这一点:在我们执行的每一步:
- 具有最大 Q(a,s) 的动作,概率为 1 - ε
- 另一个随机动作,每个都有被选中的概率ε / N(a) ,其中 N(a) 是可能动作的数量
参数ε平衡了利用与探索的权衡:同时你想要利用你已经知道的,即执行最佳动作,但是你也想要探索其他可能性,即发现可能的更好的动作。

SARSA 程序,来自1
一旦过程结束,如果我们广泛地访问每个状态-动作对,我们将有一个最优ε-贪婪 Q(a,s)* ,这很容易导致最优ε-贪婪策略:
Q-学习
在 fundo,Q-Learning 中的 Dulcis 是一种解决控制问题的非策略 TD 方法。
在非策略学习中,从独立于当前策略𝜋(𝑎|𝑠).采取的行动中学习最优值函数
Q-Learning 其实和 SARSA 很像。唯一(但有意义)的区别在于 Q(a,s) 的更新规则,它是:

Q 学习的更新规则,等式来自1
在这种情况下,q 值没有根据我们遵循的ε-贪婪策略进行更新,但是它是基于我们可以从下一个状态 Sₜ₊₁采取的最佳行动(这与 SARSA 非常不同!).

q-学习程序,来自1
同样,只要每个状态-动作值对被访问无限长的时间,Q-学习就收敛到最优 Q*(a,s) 和最优策略。
那么… SARSA 还是 Q-Learning?
我们看到了解决控制问题的两种算法,然而,它们学习的方式有所不同。在学习过程中,SARSA 由选择的ε-贪婪动作引导,这可能是(并且经常是)次优的。另一方面,在 Q(s,a) 的更新中,Q-Learning 总是跟随贪婪动作。
这种(大)差异使得 SARSA 学习的策略只是接近最优。
所以不应该一直选择 Q-Learning 吗?技术上来说你可以。但是有些情况下,选择贪婪的操作可能意味着一些风险和代价高昂的错误,您可以通过选择更保守的算法(如 SARSA)来避免或限制这些错误。
结论
在这篇文章中,我总结了 RL 的关键概念,从基本元素开始,如状态、行动和奖励的概念,到复杂的方法,以找到达到特定目标的最佳策略。
强化学习是一套复杂的方法,有时很难理解和实现,然而这些方法通常基于这里所展示的概念,因此我建议你在进一步学习之前掌握它们。
我希望这篇文章对你有用!如果是这样的话,你为什么不……
1。拍手声👏
2。跟我来❤️
3。查看我的 GitHub 页面💻
4。在 LinkedIn 上加我!🤝(我随时欢迎聊天!!!)
信用
1“强化学习:导论”,作者安德鲁·巴尔托和理查德·萨顿
SHAP 值及其在机器学习中的应用
了解 SHAP 图书馆是如何运作的

来源:图片由作者提供
SHAP 是一种解释机器学习模型预测的数学方法。它基于博弈论的概念,可以通过计算每个特征对预测的贡献来解释任何机器学习模型的预测。SHAP 可以确定最重要的特征及其对模型预测的影响。SHAP 是一个数学话题,不解释背后的数学原理就无法完全理解。然而,我们试图通过解释数学题目背后的直觉并为每个题目提供一些例子来尽可能地简化数学题目。读者也可以跳过可选章节或附录中介绍的更难的主题。我们还将使用 Python 从头开始实现不同的 SHAP 算法,以帮助你完全理解它们是如何工作的。
Python 中的 SHAP 库
在本文中,我们将研究可用于计算 SHAP 值的不同方法,每种方法都将从头开始用 Python 实现。然而,这些代码只是用来展示算法是如何工作的,对于现实世界的应用程序来说是没有效率的。Python 中的 SHAP 库可以用来高效地计算模型的 SHAP 值,在本文中,我们将简要地向您展示如何使用这个库。我们还用它来验证每一节中给出的 Python 脚本的输出(代码已经用shap 0.40.0测试过)。
解释模型的预测
大多数机器学习模型都是设计来预测目标的。这里预测的准确性非常重要,但是,我们还需要理解为什么一个模型会做出某种预测。所以,我们需要一个工具来解释一个模型。解释的意思是,我们希望对模型的预测和模型用来生成该预测的数据实例的组件之间的关系有一个定性的理解。这些组件可以是数据实例的一个特征(如果我们有一个表格数据集),或者是图像中的一组像素,或者是文本文档中的一个单词。我们想知道这些成分的存在(或不存在)如何影响预测。
机器学习中的一些模型,如线性回归或决策树,是可解释的。这里的可解释性指的是人类理解模型用来做出预测的过程的容易程度。例如,如果我们绘制一个决策树分类器,我们可以很容易地理解它是如何做出某种预测的。另一方面,深度学习模型就像一个黑匣子,我们无法轻松理解这些模式如何做出预测。SHAP 是一个个性化 模型不可知 讲解者。模型不可知的方法假设要解释的模型是一个黑盒,并且不知道模型内部如何工作。因此,模型不可知方法只能访问输入数据和待解释模型的预测。一个个性化的模型不可知的解释者本身就是一个可解释的模型。解释者可以对一个特定的数据实例做出近似相同的预测。现在,我们可以假设这个可解释的模型正在模仿原始模型用来做出单一特定预测的过程。因此,我们说可解释模型可以解释原始模型。总之,SHAP 可以解释任何机器学习模型,而不知道该模型内部如何工作,它可以使用博弈论的概念来实现这一点。所以要理解它,首先,我们需要熟悉 Shapley 值。

图 1(来源:作者图片)
沙普利值
Shapley 值是博弈论中的一个数学概念,由 Lloyd Shapley 于 1951 年提出。他后来因此获得了诺贝尔经济学奖。假设我们有一个合作博弈,有 M 个玩家,编号从 1 到 M ,设F表示玩家集合,那么F= { 1,2,.。。, M }。一个联盟, S ,定义为f(s⊆f)的子集,我们还假设空集∅是一个没有玩家的联盟。例如,如果我们有 3 个玩家,那么可能的联盟是:****

集合 F 也是联军,我们称之为大联军。很容易看出,对于 M 个玩家,我们有 2 个联盟。现在我们定义一个函数 v ,它将每个联盟映射到一个实数。 v 称为特征函数。所以,对于每个联盟 S ,数量v(S)是一个实数,称为联盟**S的价值。它被认为是联盟中的参与者在一起行动时可以获得的总收益或集体回报。由于空联盟没有玩家,我们可以假设

现在我们想知道在一个有 M 个参与者的联盟博弈中,每个参与者对总收益的贡献是多少?换句话说,在玩家之间分配总收益最公平的方式是什么?
我们可以用一个例子来说明如何解决这个问题。假设我们有一个 5 人游戏,那么 F = {1,2,...。。。, 5 }。假设我们通过一次一个地将玩家添加到空集来形成大联盟 F ,那么每次我们添加一个新玩家,我们就形成一个新的 F 联盟。比如我们先把{1}加到空集,那么当前的玩家集是{1}这是一个 F 的联盟。然后我们加上{2}并且当前集合是联盟{1,2}并且我们继续直到我们得到 F ={1,2,3,4,5}。随着每个玩家被添加到当前玩家集合中,他增加了先前联盟的总收益。例如,当当前设置为{1,2}时,总增益 v ({1,2})。加上{3}后,总增益变成 v ({1,2,3}) 。现在我们可以假设{3}对当前联盟的贡献是当前联盟(包括{3})和不包括{3}的先前联盟的值之间的差:

添加{3}后,我们可以添加{4}和{5},它们也会改变总增益。但是不影响{3}的贡献,所以前面的等式还是给出了{3}的贡献(图 2)。但是,这里有一个问题。玩家的添加顺序也很重要。

图 2(来源:图片由作者提供)
假设这些玩家是某公司某部门的员工。公司首先聘用{1}。然后,他们发现自己缺少一套技能,于是雇佣了{2}。雇用{2}后,公司的总收益增加了 10000 美元,这是{2}加到{1}上的贡献。雇用{3}后,{3}的贡献仅为 2000 美元。此外,假设员工{2}和{3}具有相似的技能。现在,员工{3}可以声称,如果他早点被聘用,他将获得相同的缴款{2}。换句话说,{3}的贡献加上{1}也可能是 10000 美元。因此,为了对每个玩家的贡献有一个公平的评价,我们还应该考虑他们加入大联盟的顺序。
事实上,要对玩家{ i }的贡献有一个公正的评价,我们应该形成 F 的所有排列,并计算{ i }在每个排列中的贡献,然后取这些贡献的平均值。例如, F ={1,2,3,4,5}的一种可能排列是:

而{3}在这个排列中的贡献是:

另一种排列可以是:

而{3}在这个排列中的贡献是:

值得注意的是,特征函数 v 将联合作为其自变量,而不是排列。联合是一个集合,所以其中元素的顺序并不重要,但是排列是元素的有序集合。在类似[3,1,2,4,5]的排列中,3 是第一个玩家,5 是最后一个加入团队的玩家。因此,对于每个排列,元素的顺序可以改变它们对总增益的贡献,然而,总增益或排列的价值仅取决于元素,而不是它们的顺序。所以:

所以,对于每一个排列 P ,我们需要先计算{ i }之前加入的玩家联盟的价值。姑且称这个联盟为。然后我们需要计算{ i }加 S 形成的联盟的价值,我们把这个联盟叫做SU {I}。现在由 ϕᵢ 表示的玩家{ i }的贡献是:**

大联盟 F 的排列总数是| F |!(这里| F |表示集合 F )的元素个数,所以我们用贡献的总和除以那个,取{ i }(图 3)的贡献的平均值。

图 3(来源:图片由作者提供)
如图 3 所示,一些排列具有相同的贡献,因为它们的联盟 S 和SU{I}是相同的。所以,一个更简单的计算情商的方法。1 是我们只计算贡献的不同值,并将它们乘以它们被重复的次数。要做到这一点,我们需要计算出每个联盟可以形成多少种排列。设f-{I}为不包括玩家{ i }的所有玩家的集合, S 为f-{I}(s⊆的联盟之一例如对于 F ={1,2,3,4,5}和{ i }={3},我们有****

S 中的元素个数用| S |,我们可以有| S |!这些元素的排列。比如说如果 S ={1,2}那么| S |=2 而我们有 2!=2 个排列:[1,2]和[2,1](图 4)。我们也知道从 S 形成的每一个排列的价值是v(S)。现在我们在从 S 形成的每一个排列的末尾加上玩家{ i }。所得排列的价值是v(SU {I})因为它们都属于联盟SU {I}。集合 F 有|F|-||S|-1 剩余元素不包括SU {I}可以加在 S 后面的元素所以,有(|F|-|S|-1)!将它们添加到SU {I}的方法。******
例如,在前面的示例中, F 的其余元素是{4}和{5}。所以我们有(|F|-|S|-1)!= (5–2–1)!=使用这些剩余元素组成大联盟的 2 种方式。这样一来,我们就有了 S !(|F|-|S|-1)!形成一个 F 的排列的方法,其中{ i }在一个 S 的排列之后,其余的玩家在{ i }之后(图 4)。****

图 4(来源:图片由作者提供)
{ i }对每个排列的总增益的贡献为:

并且{ i }对所有这些排列的总增益的总贡献是

到目前为止,我们已经涵盖了 F 中一个可能的联盟 S 的排列,并计算了{ i }对其总收益的总贡献。现在我们可以对其他联盟F-{I}重复同样的过程,得到{ i }在 F 所有排列中的贡献总和:**

最后,我们知道我们有| F |!排列为 F 。所以{ i }对 F 所有排列的总增益的平均贡献可以通过将前一项除以| F |!

这里 ϕᵢ 称为元素{ i }的 Shapley 值,它是{ i }在 F 所有排列中的平均贡献。这是玩家{ i }对 F 中所有玩家总收益的数学公平份额。正如我们之前所展示的,每个联军,都能制造!(|F|-|S|-1)!排列。由于排列总数是| F |!,我们可以写:******

Shapley 值应该具有以下属性:
1- 效率:所有玩家的贡献总和应该给出总收益:

1- 对称:如果 i 和 j 是这样的v(s∩{I})=v(s∩{j这意味着,如果两个玩家在每个可能的联盟中增加相同的收益,那么他们应该有相同的贡献。**
2- 哑元:如果 i 是这样的:对于每一个不包含 i 的联盟 S ,那么 ϕᵢ = 0。这意味着,如果一个玩家没有给任何可能的联盟增加任何收益,那么它的贡献为零。
3- 可加性:假设 u 和 v 是一个游戏的两个不同的特征函数。设其中玩家 i 的贡献分别为 ϕᵢ ( u )和 ϕᵢ ( v )(此处 ϕᵢ ( u 指 ϕᵢ 是 u 的函数)。然后我们有ϕᵢ(u+v)=ϕᵢ(u)+ϕᵢ(v)。让我们通过一个例子来阐明这个性质。假设一个员工团队从事两个不同的项目,他们对每个项目的总回报和贡献是不同的。那么如果我们把这些项目组合起来,一个员工在组合项目中的贡献就是他对每个项目贡献的总和。
我们可以很容易地证明方程 3 中的 Shapley 值满足这些性质。
证明( 可选 ):
- 如果对于每一个联盟 S ,我们有v(S)=v(S∩{I})那么我们得到:**

所以,他们有虚拟财产。它们也满足效率特性。从等式 2 我们知道:

其中cᵖᵢ表示{ i }对排列 P 总增益的贡献。现在假设这个排列的元素是:

于是我们有了|F| =M。我们可以计算出cᵖᵢ各个元素的值:**

现在如果我们把所有玩家的cᵖᵢ的值相加,那么每个cᵖᵢ中的第一项就取消了 c ᵖ

因此,对于每个排列,所有参与者的贡献总和给出了大联盟的总收益。我们知道我们有| F |!排列。因此,使用等式 2,我们可以写出:

机器学习中的 Shapley 值
但是我们如何将玩家的 Shapley 值与机器学习模型的特征联系起来呢?假设我们有一个数据集,有 N 行和 M 个特性,如图 5 所示。

图 5(来源:图片由作者提供)
这里 Xᵢ 是数据集的第 i 个特征, xᵢ ⁽ ʲ ⁾是第 j 个例子中第 i 个特征的值, y ⁽ ʲ ⁾是第 j 行的目标。这些特征的值可以形成一个特征向量,它由一个具有 M 个元素的行向量表示:

这里我们有 X ₁= x ₁、 X ₂= x ₂、… X_M = x_M (线性代数中向量通常被认为是列向量,但在本文中,我们假设它们是行向量)。特征向量也可以是数据集的第 j 行。在这种情况下,我们可以写:

也可以是数据集中不存在的测试数据点(本文中粗体小写字母(像 x )指的是向量。粗体大写字母(如 A )指矩阵,小写字母(如 x ₁)指标量值)。数据集的特征用大写字母表示,如( X ₁).一对(x⁽ʲ⁾, y ⁽ ʲ ⁾)被称为这个数据集的训练例子。现在我们可以使用一个模型来学习这个数据集:**

该函数采用特征向量 x ,这意味着它应用于 x 的所有元素。例如,对于线性模型,我们有:

所以,对于 x 的每个值,模型预测为f(x)。如前所述,这个特征向量 x 可以是训练数据集的实例之一,也可以是训练数据集中不存在的测试数据实例。例如,使用此线性模型,我们可以预测其中一个训练示例的目标:

所以,f(x⁽ʲ⁾)为数据集第j-行的模型预测,与f(x⁽ʲ⁾)和y⁽ʲ之差**
我们可以假设一个机器学习模型是一个联盟博弈, M 特征就是这个博弈中的 M 玩家。但是这个游戏的特色功能应该是什么呢?我们第一个猜测可以是f(x)本身。但请记住,一个特征函数应该满足等式 1,这意味着当我们没有球员时,总增益为零。我们没有特色(玩家)怎么评价f(x)?当一个特性不是游戏的一部分时,这意味着它的当前值是未知的,我们希望在不知道该特性的值的情况下预测模型的目标。当我们在游戏中没有特性时,意味着没有一个特性的当前值是已知的。在这种情况下,我们只能使用训练集进行预测。在这里,我们可以将训练样本的一个样本(或全部样本)的f(x⁽ʲ⁾)的平均值作为我们的最佳估计。所以,当我们没有特征时,我们的预测是:

其中 NA 表示不可用的特性(所以 f 的参数在这里都不可用)。我们还从训练数据集( k ≤ N )中采样了 k 个数据实例(特征向量)。现在我们将大联盟的特征函数定义为:

如果我们没有特征,那么使用等式 7 我们得到:

这个特征函数现在满足等式 1,可以给出大联盟f= {x₁,X* ₂ ,…,X_M }。但是我们还需要F-{I}的任何一个联盟的价值,才能够使用等式 3。我们如何将函数 f 应用于其原始参数的子集?我们可以用两种方法来做这件事。首先,我们可以仅在原始特征的子集上重新训练相同的模型(具有相同的超参数)。例如,如果联盟 S 包含了特性:*

然后我们需要这些特征的 f 的临界值,称为f【ₛ(xₛ):

这里 x ₛ 是一个向量,只包含中出现的特性的值(请注意,一个联合是由特性组成的,但是一个函数取这些特性的值)。我们既可以对联盟中存在的特征 S 重新训练同类型的模型,得到fₛ(xₛ),也可以使用原始函数 f 计算 f ₛ 。当一个特征在 S 中不存在时,那么就意味着我们不知道它的当前值,可以用 NA 代替(不可用)。**

比如说如果 F ={X₁ ,X ₂ ,X ₃ ,X ₄ ,X ₅},以及联军s= {x₁,X ₂ ,X ₅},那么我们所以:**

这里我们假设用 f 表示的模型可以处理 NA 值。因此,这个联盟的价值是:

其中fₛ(xₛ)是通过对联合中存在的特征重新训练模型或者从等式 9 中获得的。例如对于f= {x₁,X ₂ ,X ₃ ,X ₄ ,x₅}和s= {x₁,X ₂****

现在我们可以简单地使用等式 3 和等式 10 来计算特征 Xᵢ 的 Shapley 值:

请注意,在这个等式中,我们应该将ϕt124】ₓt126】ᵢ写成与等式 3 一致。然而,为了简单起见,我们使用ϕᵢ。所以,在这个等式中, i 表示第 i 个特征( Xᵢ )。通过简化前面的等式,我们得到:

其中fₛ(xₛ)为联盟中出现的特征fs,f _s∩{I}(x _ s如果我们使用 Shapley 值的效率属性(等式 5),我们可以写出:********

这意味着所有特征的 Shapely 值的总和给出了具有当前特征值的模型的预测和所有训练示例的模型的平均预测之间的差异。
模型解释的数学描述
在讨论 SHAP 值之前,我们需要一个像 SHAP 这样的解释者模型的数学描述。设 f 为待解释的原始模型,定义为:

因此,模型采用特征向量 x 和f(x)作为该特征向量的模型预测。该特征向量可以是训练数据集的实例之一(x⁽ʲ⁾)或者是训练数据集中不存在的测试特征向量。现在我们创建一组简化输入特征来显示特征向量中的特征是否存在:

向量x’称为简化特征向量。每个 x'ᵢ 都是一个二元变量,显示其对应的特征 Xᵢ 是否在特征向量中被观察到( x'ᵢ =1)或者是未知的( x'ᵢ = 0)。例如,如果你的特征向量是

然后

我们可以假设有一个映射函数将 x 映射到 x :

所以,它取简化的特征向量x’并返回特征向量 x :

解释器是一个可解释的模型 g ,它采用了 M 个二元变量:

其中 M 是等式 13 中简化输入特征的数量。行向量z’表示 x 的可用值的联合。所以 x 的零元素在 z 中总是零,而 x 的 1 元素在 z 中可以是 1 也可以是 0。我们把za联军向量。例如,如果特性的值为:

那么特征向量是:

简化的特征是:

现在一个值为 z 比如

简单的代表联盟s= {x₁, X ₃}既然只有这两个特征在 z 中有对应的 1。我们还可以断定, x '代表大联盟f= {x₁, X ₂, X ₃},所以 x '也可以认为是联盟向量。如前所述,对于单个特征向量,我们希望解释器模型的预测接近原始模型的预测。假设我们从一个联盟向量z’开始,这个向量非常接近大联盟x’。 g 对于这个联盟的预测,简单来说就是g(z’)。但是怎么才能用z’得到 f 的预测呢?问题是 f 取的是特征向量,而不是联合向量。所以我们需要映射函数 h x 来为 z 找到对应的特征值。这里hₓ(z’)返回z’中出现的特性的对应值,其他特性的值将为 NA 。例如,如果我们有

然后

f 对 z 中出现的特征的预测为:

我们还将 f ₓ 定义为 z 中出现的特征的 f 的临界值。所以我们可以写:

请注意,在等式 8 中, f ₛ 表示联合 S 中存在的特征的 f 的边缘值。不过,这里我们关注的是z’而不是联盟,f表示 f 对于z’中出现的特性的边际值。在这个例子中,我们用 z 代表联军s= {x₁, x ₃}.所以我们也可以写:**

我们希望 f 的预测值(即f(z)与 g (即g(z’)非常接近,以确保 g 模仿f的相同过程总而言之,我们想要**

能够声称 g 能够解释 f 。
我们可以根据 g 对解释方法进行分类。附加特征归因方法有一个解释器模型,它是二元变量的线性函数:

其中 ci 是一些常数。正如我们后来看到的,SHAP 属于这一类方法。所以我们想用z’来表示方程 11。设 x 为特征向量,x’为其简化特征向量。我们可以证明,Shapley 值可以表示为:

其中 ϕᵢ ( f , x )强调的是沙普利值是 f 和 x 的函数。这里我们考虑x’的所有可能的联盟向量(对应于 x 的所有联盟)。对于每个联合向量z’,我们计算特征 i 的贡献。| z '|是 z '(对应大联盟的大小)中非零项的个数,|x'|是 x '(大联盟的大小)。z' *I表示对应的联盟不包含特征{ i }。所以,z' *I表示将 z '的第 i 个元素设置为 0(z'ᵢ= 0)得到的联合向量。例如,如果 3 代表第三个特征 x ₃,那么我们可以写成:**

很容易证明等式 11 和等式 18 是等价的。
证明( 可选 ):首先注意,等式 11 中的| F |等于等式 18 中的|x'|因为它们都是指特征总数, M 。记住等式 11 中的每个联盟 S 不包括特征 i 自s⊆f-{I}。我们很容易看到,方程 11 中的每个联盟 S 在方程 18 中都有对应的值z' *I*。例如,如果我们有:**

然后我们得到:

而z' *I为S的对应值为【1 1 0 1】。***

通过替换等式 11 中的这些值,该值的对应项 S 为:

在等式 18 中,这个值’的对应项是:**

现在,根据等式 15,我们得出结论,等式 19 和等式 20 给出了相同的结果。一般来说,对于等式 11 中的每个联盟 S ,我们都有一个z' *I即来代表它。因此, z 也代表S∩{I}, 并且我们可以将ft30】ₛ(xₛ)和f _s∩{I}(x _ s∩{I})替换为 f 另外,| S |和| F |分别等于| z '|-1 和| x '|。因此,等式 11 中的每一项(对于不包括 i 的联合 S )在等式 18 中具有相应的项,其中联合向量z’包括 I ,并且这两项给出相同的值:*

但这只包括等式 18 中包含{ i }的z。在等式 18 中,我们还可以有不包括{ i }的 z 。对于一个不包括{’的联合向量,我们可以写成fₓ(z’)=f****ₓ********

它没有给方程 18 增加任何东西。因此,我们得出结论,等式 11 和等式 18 是等价的,并给出相同的结果。∎
需要注意的是,在等式 18 中,空联盟的联盟向量(z' =[0 0…0])不包括在求和中。对于这个联合向量,我们有:

这是没有定义的。即使能计算出来,这个联盟也不会增加任何东西

还请注意,在介绍 SHAP 方法的原始论文中,等式 18 的写法不正确(参见1中的等式 8)。等式 11 是经典的 Shapley 值等式,在这个等式中,我们只关注可用的特性。等式 18 引入了缺失的概念。这里的大联盟向量 x '可以有一些缺失值,其中 x ' ᵢ =1。等式 18 具有一些有趣的性质,描述如下:
属性 1(局部精度)
设g(x’)为解释模型,定义为

其中ϕ₀=e[f(x)】、 ϕᵢ 为 f 的沙普利值。假设我们有一个特征向量 x ,它的简化特征向量是 x ',那么我们有x=h【ₓ(x')。然后基于这个性质,预测的 g 为x'匹配原模型的预测 f 为x 。所以,我们可以写:**

证明( 可选 ):假设|f| =m(特征数),并且所有特征都可用( x ' ᵢ =1 对所有 i )我们可以用等式 12 来写:**

其中ϕ₀=e[f(x)】。∎
如前所述,等式 11 仅考虑可用特征。因为等式 12 是从该等式导出的,所以它仅包括可用特征的 Shapley 值。例如,如果最后一个特征不可用,那么从等式 12 我们得到

然而,这个结果仍然与等式 22 一致,因为对于最后一个特征,我们有 x '_ M =0。
属性 2(缺失)
缺失要素的 Shapley 值应该为零。

证明( 可选 ):这不是方程 11 中经典 Shapley 值的必要条件。因为它不包括缺失的功能。然而,如果我们使用公式 18 计算 Shapley 值,那么我们可以证明它满足这一特性。考虑从 x 导出的所有联盟向量z’。如果 x ' ᵢ =0,那么 z ' ᵢ =0。所以,对于这些联合向量我们得到z' *I=z',我们可以写出:*

属性 3(一致性)
一致性意味着改变原始模型来增加一个特征对模型的影响将永远不会减少它的 Shapley 值。从数学上讲,如果我们有一个单一的特征向量 x ,以及两个模型 f 和f’(两个模型 f 和f’都是 x 的函数),那么

对于所有输入z’,则

请记住,在等式 18 中

与特征 Xᵢ 对预测的贡献成比例。因此,如果将模型从 f 改为f’,并且得到特征 Xᵢ 对预测 x 增加的更高贡献(或保持不变),那么 Xᵢ 的 Shapley 值永远不会减小。
证明( 可选 ):还是那句话,很容易说明等式 18 满足这个性质。因为我们对于两个模型具有相同的特征向量 x ,所以我们将具有相同的x’。现在对于各联军的矢量【我们有****

所以,通过为所有的增加这些术语,我们得到:**

现在我们已经熟悉了 Shapley 值及其属性,我们可以看到它们如何解释机器学习模型。假设我们有一个模型 f ,一个特征向量 x 。我们将模型 g 定义为:

其中ϕ₀=e[f(x)】,ϕᵢ为 f 的 Shapley 值, x 为x的简化特征向量(所以 x = 模型 g 是线性的,所以是可解释的。另外基于性质 1,g(x')=f(x),因此 g 可以完美地模拟 f 进行单次预测f(x),并且可以作为的解释器模型可以看出,对于附加特征归属方法,上面定义的模型 g 是遵循等式 17 并且满足性质 1、2 和 3 的唯一可能的解释器模型。总之,Shapley 值可以提供线性模型的系数,该系数可以用作任何机器学习模型的完美解释器。****
从沙普利值到 SHAP 值
Shapley 值具有坚实的理论基础和有趣的性质,然而,在实践中,计算它们并不容易。为了计算它们,我们需要计算等式 18 中的fₓ(z’)或等式 11 中的fₛ(xₛ)。记住这一点

所以,计算fₓ(z’)或fₛ(xₛ)意味着我们需要计算f(x)带有一些 z 中没有的缺失特征问题是大多数模型不能处理缺失值。比如在线性模型中,我们需要 xᵢ 的所有值来计算f(x)。所以,我们需要一种方法来处理f(x)中缺失的值。如前所述,对于每个联盟 S , x 的缺失元素是 S 中不存在的特征值。为了计算ft60】ₛ(t63】xₛ**,我们假设:

这里e[f(x)|xₛ]是f(x)的期望值,以中出现的特性为条件。同样,我们可以写:

使用等式 23 或等式 24 中的条件期望计算的 Shapley 值被称为 SHAP (SHapley 加法解释))值。因此,为了从等式 11 获得 SHAP 值,我们可以写为:

为了从等式 18 计算 SHAP 值,我们可以写出:

SHAP 是一种附加特征归因方法,其中我们有一个线性解释器模型。在本文中,我们讨论两种方法来计算方程 23 或方程 24 中的条件期望。第一个在本节中讨论,第二个适用于树结构数据的将在后面讨论。
设表示联军 S 的补充。所以,表示中不存在的部分原始特征。现在我们可以用全概率定律来写:******

其中 f ( x_S̅ , x ₛ )表示 f 的部分参数属于 x ₛ ,其余参数属于 x_S̅ 。当然, x ₛ 或者 x_S̅ 的参数不一定是连续排列的。p(x_s̅|xₛ)是 x_S̅ 给定 xₛ 的条件概率。所以,要计算fₛ(xₛ)的值,我们需要条件概率p(x_s̅|xₛ)。不幸的是,我们大部分时间都不知道这个分布。因此在 SHAP,我们假设这些特征是相互独立的,所以:**

将该等式代入等式 27,我们得到:

因为我们有离散的数据点,我们可以用一个和来近似这个积分。我们从训练数据集( k ≤ N )中抽取 k 个数据实例(特征向量),并将它们分别称为x⁽ʲ⁾.每个数据实例中的特征或者属于 S 或者属于 S̅ 😗*

然后,对于每个数据实例,我们将出现在中的特征值替换为它们在 x ₛ 中的对应值,并对该数据实例进行预测:

现在,前面的积分可以用这些预测的平均值来近似:

图 6 显示了计算该积分的示例。同样,我们可以写:

其中是在 z 中具有非零索引的特征集合。图 6 显示了这种方法的一个例子。这里我们有一个模型 f ( x ₁、 x ₂、 x ₃、 x ₄、 x ₅)其中xₛ**= {x₁、 x ₃、 x 所以x_s̅= {x₂、 x ₅}和特征 X ₂和 X ₅是 x 中的娜。特征向量 x 的形式为x=【x₁纳x₃x₄纳。为了计算f(x),我们需要缺失特征的值,因此我们从训练数据集的样本中借用它们的值。对于该样本的第 i 个数据实例,我们将特征向量x(x₂, X ₅)的缺失值替换为该实例中的对应值( x ₂^( i ), x ₅^( i xₛ)=f(x₁,x₂^(I), x ₃, x ₄,x₅^( 因此,对于每个数据实例,我们现在都有一个预测。最后我们取这些预测的平均值,报为我们对f(x)的估计。现在,我们可以使用公式 29 和公式 30 给出的近似值来计算公式 25 或公式 26 中的 SHAP 值。**

图 6(来源:图片由作者提供)
请注意,等式 30 也与等式 7 一致。当 z 的所有元素都为零时, S 成为空集,所以我们只对训练集的 k 数据实例取模型预测的平均值。
线性 SHAP
假设我们的预测模型 f 是线性回归模型:

其中 x ₀=1,特征 Xᵢ 、 i = 1、…、 M 相互独立。现在我们可以证明 SHAP 值由以下等式给出:

其中 k 是我们用来计算 SHAP 值的训练数据集样本中的数据实例数量。你可以参考附录中的证明。我们也可以直观地驱动这个方程。在线性模型中,特征 Xᵢ 对f(t20】xt22)的贡献简单来说就是 cᵢxᵢ 。所以我们可以写:****

然而,等式 22 增加了对 Shapley 值的约束:

所以我们减去

从方程 31 中的 cᵢxᵢ 来满足方程 22。如果我们将等式 31 中的 Shapley 值相加,我们会看到结果与等式 22 一致:

用 Python 计算 SHAP 值
线性 SHAP
作为我们的第一个例子,我们使用 Python 来计算虚拟数据集的线性 SHAP 值(等式 31)。我们首先定义数据集。我们只有两个填充了一些随机数的特征,目标被定义为这些特征的线性组合。这些特征是独立的。
**# Listing 1# Defining the dataset
X = pd.DataFrame({'a': [2, 4, 8, 0, 3, 6, 9],
'b': [1, 5, 0, 7, 1, -2, 5]})
y = 5*X['a'] + 2*X['b'] + 3**
然后,我们对该数据集使用线性回归模型,并计算该模型的系数,这些系数与用于定义目标的系数相同。
**# Listing 2# Defining a linear model
linear_model = LinearRegression()
linear_model.fit(X, y)print("Model coefficients:")
for i in range(X.shape[1]):
print(X.columns[i], "=", linear_model.coef_[i].round(4))**
Output:
**Model coefficients:
a = 5.0
b = 2.0**
最后,我们使用等式 31 来计算该数据集第一个示例的 SHAP 值:
**# Listing 3shap_values = ((X[:1] — X.mean()) * linear_model.coef_)
shap_values_table = shap_values.T
shap_values_table.columns = ['SHAP_value']
shap_values_table**

所以,我们有:

其中 1 和 2 分别指特征 a 和 b 。
使用 SHAP 库的线性 SHAP
我们还可以使用 SHAP 库来计算清单 2 中定义的线性模型的 SHAP 值:
**import shap
explainer = shap.LinearExplainer(linear_model, X)
shap_values = explainer.shap_values(X[:1])
shap_values**
Output:
**array([[-12.85714286, -2.85714286]])**
SHAP 图书馆中的类LinearExplainer()采用训练模型和训练数据集。这个类中的方法shap_values()获取要解释的行的数组,并返回它们的 SHAP 值。请注意,这里我们得到了与清单 3 相同的结果。
精确的 SHAP 值
在下一个示例中,我们计算具有特征相关性的线性模型的 SHAP 值。所以,我们不能用等式 31。相反,我们使用等式 11,并使用所有可能的联合来计算 SHAP 值。这里我们使用scikit-learn图书馆中的波士顿数据集:
**# Listing 4d = load_boston()
df = pd.DataFrame(d['data'], columns=d['feature_names'])
y = pd.Series(d['target'])
X = df[['LSTAT', 'AGE', 'RAD', 'NOX']]
X100 = X[100:200]linear_model2 = LinearRegression()
linear_model2.fit(X, y)**
波士顿数据集有 13 个特征,但我们只选择其中的 4 个(LSTAT、AGE、RAD、NOX)。我们还对该数据集的 100 行进行采样,以估计等式 29 中的fₛ(xₛ)(因此 k =100)。我们将这些行存储在X100中。然后我们用这个数据集来训练一个线性模型。现在我们需要定义一些函数来计算 Python 中的 SAHP 值:
函数coalition_worth()用于计算联盟的价值。它需要一个模型、一个训练数据集的样本(X_train)、一个数据实例(x)和一组联盟(coalition)。这里,在等式 11 中,coalition 代表 S 。这个函数用联合集中给定的值替换X_train的列,然后它使用模型来预测所有这些行的目标。最后,取所有这些预测的平均值,并将其作为ft35】ₛ(xₛ)的估计值返回。
函数coalitions()返回一个数据实例的所有联合的集合,不包括特征col。所以,它计算等式 11 中F-{I}的所有联盟,其中 col 表示特征 i 。**
函数coalition_contribution()计算等式 11 中每个联盟的贡献(等式 11 中求和的每一项)。这里我们用了这样一个事实:

因此scipy中的函数comb()被用于计算二项式系数:

最后,函数calculate_exact_shap_values()获取待解释的特征向量(X_explain)并计算其中每个特征向量的 SHAP 值。它将每个联盟的贡献相加,以计算特征向量中每个特征的 SHAP 值。现在,我们可以使用该函数,通过数据集行的样本来计算 Boston 数据集第一行的 SHAP 值(X100):
**# Listing 6calculate_exact_shap_values(linear_model2, X100, X.iloc[0])**
Output:
**(22.998930866827823,
[[7.809214247585507,
-0.7308440229196315,
0.1290501127229501,
0.23758951510828266]])**
使用 SHAP 库计算 SHAP 值
shap库中的类Explainer()接受模型预测函数(不仅仅是模型)和训练数据集,方法shap_values()返回 SHAP 值。如果我们不传递特定算法的名称,它会根据给定的模型和训练数据集,尝试找到计算 SHAP 值的最佳算法。这里我们将它用于清单 4 中定义的同一个模型。
**explainer = shap.Explainer(linear_model2, X100)
shap_values = explainer.shap_values(X.iloc[0:1])
shap_values### Output:
array([[ 7.80921425, -0.73084402, 0.12905011, 0.23758952]])**
**explainer.expected_value### Output:
22.998930866827834**
这里,数组 shap_values 给出了 ϕ ₁到 ϕ_M 的值。 ϕ ₀的值存储在 Explainer 的 expected_value 字段中。这里我们得到了与清单 6 中的calculate_shap_values()几乎相同的 SHAP 值。请务必注意,Explainer 类会自动对 100 行训练数据进行采样(如果行数大于 100),并使用这些数据来计算 SHAP 值。因此,如果我们使用超过 100 行的训练数据集,Explainer的输出将不再匹配calculate_exact_shap_values()的输出:
**explainer = shap.Explainer(linear_model2, X[:150])
shap_values = explainer.shap_values(X.iloc[0:1])
shap_values### Output:
array([[ 8.88370884e+00, -2.97655621e-01, 1.17561972e-01,
-1.48202335e-03]])**
**calculate_exact_shap_values(linear_model2, X[:150], X.iloc[0])### Output:
(22.521908424669917,
[[7.993180897252836,
-0.11946396867250808,
0.11973195423751992,
-0.07141658816282939]])**
内核 SHAP
要使用公式 11 或公式 18 计算模型的 SHAP 值,我们需要计算所有可能的联合。如前所述,对于 M 特征,可能联盟的总数是 2。当然,在等式 25 中我们计算了F-{I}的所有联盟。所以,我们需要计算每个 SHAP 值的实际联盟数是 2 ᴹ ⁻,对于 M 特征的时间复杂度是**

对于每个联盟,我们需要估计ft30】ₛ(xₛ)和f _t39】s∩{I}(x _ s∩{I})使用所以该算法的时间复杂度为o(km2ᴹ)。**
基于这一结果,当 M 增加时,可能联盟的数量呈指数增加,并且当我们具有多个特征时,使用这些方程来寻找 SHAP 值在计算上变得难以处理。核 SHAP 是一种近似方法,可以用来克服这个问题。这种方法首先是由 Lundberg 和 Lee 1提出的。
石灰
为了理解内核 SHAP,我们应该首先熟悉另一种称为 LIME(本地可解释模型不可知解释)的模型不可知解释方法。LIME 是在 SHAP 之前开发的,它的目标是为一个分类器确定一个可解释的模型,这个模型在本地是忠实的。假设您有一个模型 f ( x ),并且您想要为其找到最佳可解释模型 g ( z' )(记住 g 是联合向量 z ')。设 x 为待解释的特征向量, x 为其联盟向量。
现在我们需要在 x 附近找到一些随机联盟向量。例如,我们可以选择 x 的一些非零分量,以 0.5 的概率将它们从 1 变为 0,以产生联盟向量z’。结果,我们在x’附近得到了一些联合向量。我们一般称这些联合向量中的每一个为Z’,我们称所有这些向量的集合为 Z 。我们可以假设 x '和 z '是一个M-维空间中的一些点(图 7)。我们可以使用映射函数 h ₓ 找到特征空间中每个z’对应的向量(图 7)😗*

我们需要对x’和每个z’之间的距离有一个量化的度量。于是,我们将函数π(z’)定义为x’与【z’之间距离的度量。这个函数将联合向量z’映射到一个非负实数({0,1} ᴹ → R≥ 0)。π(z’)应该增加为z‘越来越接近x’。****

图 7(来源:作者图片)
现在假设我们有一组可解释的讲解者模型 G ,我们想为f(x)其中(G∈G)找到最准确的讲解者模型 g 。根据等式 16,我们需要:

因此,我们可以定义一个损失函数 L ( f,g,π),它与f(z)和g(z’)之间的距离成正比我们希望g(z’)非常接近f(z)当z’非常接近x’。但是 Z 中的一些点可能与×的’不是很接近,所以我们需要为它们增加一个惩罚项。由于π是【x’和【z’之间距离的度量,我们可以将其作为参数添加到损失函数中。例如,我们可以将损失函数定义为:****

通过将π添加到损失函数中,我们为中远离的点添加更高的惩罚,并且更近的点对于最小化变得更重要。因此,损失函数决定了解释函数 g 在非常靠近 x 的点 z 【处】近似 f 的程度。现在我们需要在【G(我们想要尝试的所有可解释函数的集合)中找到函数 g ,最小化这个损失函数。******
我们也喜欢更简单和更易解释的函数,所以我们让ω(g)作为解释函数 g 的复杂性(相对于可解释性)的度量。比如对于线性模型,ω(g)可以定义为非零权值的个数,或者对于决策树,可以定义为树的深度。因此ω(G)随着 g 变得更简单从而更容易理解而减小,我们应该在 G 中寻找一个函数 g 来最小化下面的目标函数:

这相当于解决这个最小化问题:

现在我们可以用下面的定理来定义核 SHAP 方法:
定理 1 。假设我们有一个具有 M 个特征的特征向量 x 和一个以 x 为输入的模型 f 。设 g 为线性模型,定义如下:

其中 z ' ᵢ 是z的第 i 个元素,是 x 的联合向量;ϕ₀=ef(x)和 ϕᵢ (对于 i > 0)是 f 和的 Shapley 值当 M 趋于无穷大时,方程 32 的解(函数 g )接近方程 33 给出的函数,如果 L 、ω*、π 、 ₓ 定义为:*****

其中| z '|是 z '中非零项的个数。
这个定理的证明在附录中给出。有趣的是,在介绍 SHAP [2]的原始论文中,这个定理没有被正确证明(更多细节请参考附录)。让我们看看如何在实践中使用这种方法。我们假设我们有一个具有 M 特征的特征向量 x 。我们计算简化的特征向量x’。这里我们为了简单起见假设 x 中没有 NA s(在定理 1 中, x 可以有 NA 值),那么 x 的所有元素都是 1。然后我们计算x’(它们也被称为x’)的所有可能的联合向量。x’的每个联合向量称为z’**ᵢ,是一个有 M 个元素的向量,其中每个元素可以是 0 也可以是 1。我们有 2 个 ᴹ 联盟向量,我们将所有这些联盟向量放入 2 个 ᴹ× M 矩阵 X 😗*

这个矩阵叫做联盟矩阵。对于每个联盟向量【z’ᵢ,我们可以计算出模型预测fₓ(z【'ᵢ】。我们将列向量 y 定义为:**

其中【ϕ】₀=ef(x)。****
最后,我们将 2 ᴹ× 2 ᴹ 对角矩阵 W 定义为:

在哪里

例如,假设特征向量为:

然后我们有:

并且联合矩阵将具有 2 =8 行:

我们有:
********
根据等式 32 和等式 34,我们知道需要解决这个最小化问题,以获得 Shapley 值的估计值:

我们将对最优函数 g 的搜索限制为具有以下形式的线性函数:

列向量 c 定义为:

现在可以证明(详情见附录):

因此,最小化问题等价于

g 只是 cᵢ 的一个函数,所以我们不去找最小化目标函数的函数 g ,而是去找最小化它的向量 c 的值。函数πₓ(z’ᵢ)也被称为沙普利核权重。基于(等式 40),πₓ(z'ᵢ)的每个值就像一个重量为(g(z'ᵢ)-fₓ(z【z】 这也是联盟矢量z'ᵢ的一个砝码,可见联盟是多么的重要。****
如附录所示,这个最小化问题的解决方案是:

根据定理 1,我们知道这个解是 Shapley 值的近似值:

由于ϕ₀=e[f(x)],我们有所有的沙普利值。等式 41 中的 R 项并不依赖于一个具体的数据实例来解释,所以如果要解释多个数据实例,只需要计算一次 R 。然后,对于每个数据实例,计算新的值 y 并乘以 R 以获得 SHAP 值。**
需要注意的是 X 中联盟向量的顺序并不重要。例如,在等式 39 中,联合向量[0 0 0]是第一行×第一行*** ,然而,它可以是最后一行,等式 41 仍然有效(如果你看附录中定理 1 的证明,我们不对联合向量的顺序作任何假设z'ᵢ作为矩阵×的行它只需要拥有所有 2 个ᴹ联盟向量。***
Python 中的内核 SHAP
即使特征向量 x 具有一些不可用的特征,定理 1 也是有效的,但是在实践中,我们假设 x 的所有特征都可用来实现 SHAP。所以, x 的所有元素都是一,并且|x' | =M。现在让我们看看如何用 Python 实现内核 SHAP。为此,我们首先需要一个 Python 函数来计算πₓ(z'ᵢ)。**
函数pi_x()获取一个联合向量z'ᵢ(作为一个列表),并基于等式 38 返回π【ₓ(z'ᵢ)的值。函数generate_colaition_vectors()获取特征的数量(num_features,并为它们生成所有可能的联合向量。
例如:
***generate_coalition_vectors(num_features)***
Output
***[[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],
[1.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 1.0]]***
现在我们需要一个函数来生成等式 37 中矩阵 W 的对角元素。这里我们有一个问题。通过看方程 38 我们看到如果|z'ᵢ| = 0(当z'ᵢ都为零)和|z'ᵢ| =m(当的所有元素请记住,我们希望最小化以下目标函数(等式 40):**

让我们假设第一个z’₁是所有元素都为零的联盟:

让最后的联盟是所有元素是一个联盟:

我们知道,当我们没有可用的特征时,模型预测是训练数据集的样本中的实例的预测的平均值(等式 7)。所以,我们有:

我们还知道,当所有功能都可用时:

所以,对于最后一个联盟,我们有:

现在在方程 42 中,π(z【'₁】是这一项的权重:**

由于πₓ(z'₁)是无穷大,我们需要这个项为零,这意味着我们应该有:**

这是基于 Shapley 值的线性模型的属性(等式 22)。π(z’_ 2)的无穷大值意味着我们应该有:****

我们还知道,随着 M 趋于无穷大,最小化目标函数的 cⱼ 的值越来越接近于 f 和x* 的 Shapley 值。因此,上面的等式只是显示了基于 Shapley 值的线性模型的属性(等式 22)。现在我们来画πₓ(z'ᵢ)为一个特例,其中 M =13。为此,我们可以使用清单 9:*****
**# Listing 9pi_values = [1e7]
for i in range(1, 14):
try:
pi_values.append(pi_x(13, i))
except:
pi_values.append(1e7)
plt.plot(pi_values, marker=’o’)
plt.xlabel(“$|\mathregular{z}’_i|$”, fontsize=14, weight=”bold”,
style=”italic”,)
plt.ylabel(“$\pi_\mathregular{x}(\mathregular{z}’_i)$”, fontsize=14,
weight=”bold”, style=”italic”,)
plt.ylim([0, 0.1])
plt.show()**
结果如图 8 所示。我们对π(z'₁)和πₓ(z' _ 2ᴹ)都用了一个大常数(1e7而不是无穷大,以便能够把它们画出来。****

图 8(来源:图片由作者提供)
剧情是对称的,随着|z'ᵢ|越来越接近 0 或者 M 、πₓ(z'ᵢ)增加。π的每一个值(z'ᵢ)就像是一个重量对于联军向量 z 'ᵢ.如前所述,我们用等式 30 来估算f**(’)。对于 z '₁我们知道******

而对于’_ 2我们知道****

这两个联合向量是最重要的,因为我们可以为它们精确计算出fₓ(z’ᵢ)的值。因此它们有无限的重量。但是对于其他联盟(z'ᵢ)我们只能估计fₓ(z'ᵢ)。当联军z'ᵢ越来越接近 z '₁或者z' _ 2ᴹ*、π ₓ 赋予它高得多的权重。*****
函数generate_pi_values()生成 W 的对角元素。对于每个联盟矢量z'ᵢ,这个函数使用pi_x()计算πₓ(z'ᵢ)。我们不能用pi_x()来表示 z '₁和z' _ 2m,因为它以被零除异常结束。相反,我们把πₓ(z'ᵢ)换成大常数1e7用于其中的联盟|z| = 0 和|z*'ᵢ| =*****
然后我们需要一个 Python 函数为每个联盟向量计算fₓ(z'ᵢ)。为此,我们可以使用等式 30。假设我们要说明的特征向量是 x 。我们把 x 中的所有特征都聚集到一个集合中,这个集合叫做’【ᵢ】xₛ。然后,对于训练数据集样本中的每个数据实例,我们将出现在中的特征值替换为它们在 x ₛ 中的对应值,并对该数据实例进行预测:******

我们将训练集的所有实例的这些预测的平均值作为对fₓ(z’ᵢ)的近似:****

Python 函数f_x_z_prime()使用这种方法为每个联盟向量计算fₓ(z'ᵢ)😗***
函数kernel_shap()采用模型预测器、训练数据集、其权重数组和将由核 SHAP ( X_explain)解释的特征向量。它生成联合向量,E[f(x)],以及矩阵的对角元素【W。然后调用函数calculate_shap_values()计算X_explain中每个特征向量的 SHAP 值。对于要解释的每个特征向量,该函数使用等式 41 计算 SHAP 值。形成矩阵 X 和 W 和列向量 y 。然后用等式 41 计算出 ϕ ₁到 ϕ_M 。它返回一个元组,其中第一个元素是 ϕ ₀,第二个元素是X_explain中每个特征向量的 SHAP 值( ϕ ₁到 ϕ_M )的数组。****
现在让我们在一个数据集上尝试kernel_shap()。我们再次使用波士顿数据集,并且我们包括所有特征(13 个特征)。然后我们在那上面训练一个随机森林回归器。
**# Listing 13d = load_boston()
df = pd.DataFrame(d['data'], columns=d['feature_names'])
X = df[['AGE', 'RAD', 'TAX', 'DIS', 'RM', 'LSTAT', 'B', 'INDUS',
'CHAS']]
y = pd.Series(d['target'])rf_model = RandomForestRegressor(max_depth=6, random_state=0, n_estimators=10).fit(X, y)**
现在我们可以在这个数据集上尝试kernel_shap()。我们使用X数据集的前 420 行作为训练数据集,然后尝试解释第 470 行。在这个例子中,训练数据集的所有元素(X_train)具有相同的权重。
**# Listing 14X_train = X.iloc[:100].copy()
data_to_explain = X.iloc[470:471].copy()
weights = np.ones(len(X_train)) / len(X_train)
shap_values = kernel_shap(rf_model.predict, X_train.values, weights,
data_to_explain)
shap_values**
Output:
**(22.74244968353333,
array([[ 4.05579739e-02, -4.91062082e-02, -4.69741706e-01,
9.28299842e-02, -8.88366342e-01, -2.86693055e+00,
2.19117329e-01, -3.57934578e-02, 7.10305024e-09]]))**
输出是一个元组。这个元组的第一个元素给出了 ϕ ₀ (22.742),第二个元素是一个数组,分别给出了 ϕ ₁到 ϕ ₁₃的值。
需要注意的是,训练数据集中的样本数量对内核 SHAP 的运行时间有很大的影响。为了计算等式 36 中的 y ,我们需要计算I= 2…2ᴹ的fₓ(z'【ᵢ)并计算fₓ(因此,大型训练集可能会降低计算速度,为此,我们仅使用训练集的一小部分样本。我们可以从训练数据集中随机抽取 k 个数据实例,或者使用聚类算法从训练数据集中抽取样本。例如,我们可以使用 k -means 在训练数据集中查找 k 个聚类。每个聚类的权重与其中数据点的数量成比例。对于每个聚类,我们计算其均值和权重,并用一组加权均值来总结训练数据集。****
核 SHAP 方程的替代形式
我们还可以使用一个技巧从目标函数中移除 z '1 和z' _ 2ᴹ。记住,目标函数中对应于 z '₁和z’_ 2ᴹ的项与等式 22 中描述的 Shapley 值的性质相关:****

我们可以去掉目标函数中对应于 z '₁和z' _ 2ᴹ的项,并加入上述方程作为单独的约束。因此,等式 40 中的目标函数变为:****

考虑等式 35 中定义的联盟矩阵 X 并且让 z '₁和z' _ 2ᴹ分别是全零和全一联盟。设 X ₜ 为 a (2 ᴹ -2) ×M 矩阵,该矩阵是通过从联合矩阵zz' _ 2ᴹ*x*****

并设 X ᵣ 为一个(2ᴹ-2)×(m-1)矩阵定义为:****

*这里的 、i 是指 X ₜ 的第 i 列。于是 X ᵣ 由第 M -1 列减去最后一列 X ₜ 而成。我们还将列向量 c ᵣ 定义为:

并将列向量 y ᵣ 与 2 个 ᴹ -2 元素定义为:

其中 x 是应该说明的特征向量。最后,我们将(2ᴹ-2)×(2ᴹ-2)对角矩阵 W ᵣ 定义为:****

请注意 W ᵣ 现在已经没有无限对角元素了。可以看出(细节在附录中给出),等式 45 中的目标函数可以写成:

所以,我们需要解决这个最小化问题:

而使这个目标函数最小化的 c ᵣ 的值是:

因此,基于定理 1,我们有:

我们知道这一点

一旦我们有了 ϕ ₁到 ϕ_M -1,我们就可以使用等式 22 计算 ϕ_M :

所以,用这种方法我们可以计算所有的 SHAP 值而不用处理 W 的无限元素。类似于等式 41,术语 R ᵣ 并不依赖于一个具体的数据实例来解释,所以如果要解释多个数据实例,只需要计算一次即可。为了使用等式 47 实现内核 SHAP,我们需要更改一些 Python 函数。清单 15 中的函数generate_coalition_vectors2()类似于清单 8 中定义的函数,但是它不生成全 0 和全 1 联盟。所以它只生成矩阵的行 X ₜ.
函数generate_pi_values2()类似于清单 10 中定义的函数generate_pi_values(),但是它没有异常处理,因为我们没有π【ₓ(z'ᵢ)的无穷大值。此函数返回矩阵对角线矩阵 W ᵣ 的对角线元素列表。**
函数kernel_shap2()通过调用函数calculate_shap_values2()计算 SHAP 值。它使用等式 47 计算出 ϕ ₁到 ϕ_M -1,然后使用它们计算出 ϕ_M 。
我们可以在清单 13 中定义的之前的模型和数据集上尝试kernel_shap2()。
**# Listing 18shap_values = kernel_shap2(rf_model.predict, X_train, weights,
data_to_explain)
shap_values**
Output:
**(22.74244968353333,
array([[ 4.05579906e-02, -4.91062100e-02, -4.69741717e-01,
9.28299916e-02, -8.88366357e-01, -2.86693056e+00,
2.19117328e-01, -3.57934593e-02, 4.44089210e-16]]))**
正如您看到的,返回的 SHAP 值几乎与清单 14 中的kernel_shap()相同。
内核 SHAP 与采样
当一个模型有如此多的特征时,计算等式 41 或等式 47 的右边仍然是计算上昂贵的。在这种情况下,我们可以使用联合向量的样本’’ᵢ来形成联合矩阵×t40】。联盟矩阵 X 有 2 ᴹ 行,每一行都是一个联盟向量z’ᵢ.沙普利内核权重,πt54】ₓt56】(t58】zt61】'ᵢ,给出了联军向量的权重z'ᵢ。然而,大多数联合向量具有非常小的 Shapley 核,这意味着它们对 Shapley 值没有贡献那么多。因此,我们可以忽略这些联合向量,并在没有它们的情况下近似 Shapely 值。******
如果我们假设 Shapley 核权重给出了联合向量的概率分布,我们可以从不包括'₁和z' _ 2ᴹ的原始 2 个 ᴹ-2 联合向量中采样(替换)D 联合向量的子集。我们将这些向量放在 D×M 联合矩阵X _p中:****

其中 z '₁...z’ᴅ为采样的联合向量。现在,我们可以使用等式 47,通过这个新的联合矩阵来计算 Shapley 值。我们构成 D× ( M -1)矩阵 X ᴅ 为:****

列向量 c ᴅ 定义为:

并且我们用 D 元素将列向量yt36】t37】ᴅ定义为:

最后,我们将 D×D 对角矩阵 W ᴅ 定义为:

所有 Shapley 核权重为 1 的原因是我们已经使用它们的 Shapley 核权重对联合向量进行了采样。因此,采样的联盟现在在新的联盟矩阵中被同等地加权。现在,我们可以使用等式 47,通过采样的联合向量来计算 Shapley 值:

现在,我们可以使用公式 47 的修改版本,利用采样的联合向量来计算 Shapley 值。要用 Python 实现这个方法,我们只需要修改清单 17 中定义的函数:
在kernel_shap3()中,我们使用Numpy中的函数choice()来使用它们的归一化 Shaply 核权重对联合向量进行采样。这里,我们对原始联合向量的一半进行采样(但它可以是不同的数字)。采样的联合向量将被传递到calculate_shap_values3()以计算 SHAP 值。这次我们不需要πₓ(z'ᵢ)的值,因为它们都等于 1。**
SHAP 图书馆里的内核 SHAP
类kernelExplainer()可用于使用核 SHAP 方法计算 SHAP 值。我们使用清单 13 中定义的相同数据集和模型。该类获取模型和训练数据集,其方法shap_values()将 ϕ ₁返回到 ϕ_M 。 ϕ ₀的值存储在字段expected_value中。
**explainer = shap.KernelExplainer(rf_model.predict, X_train)
k_shap_values = explainer.shap_values(data_to_explain)
phi0 = explainer.expected_value
k_shap_values### Output:
array([[ 0.04055799, -0.04910621, -0.46974172, 0.09282999, -0.88836636, -2.86693056, 0.21911733, -0.03579346, 0\. ]])**
**phi0### Output:
22.742449683533327**
如您所见,输出几乎等于清单 14 中的kernel_shap()或清单 18 中的kernel_shap2()。与类Explainer不同,KernelExplainer不会自动从训练数据集中采样,而是使用传递给它的整个训练数据集。我们既可以像上面的例子那样手动采样数据集,也可以使用方法shap.sample()和shap.kmeans()来完成。例如,我们可以使用shap.sample()随机抽取 100 行训练数据集:
**explainer = shap.KernelExplainer(rf_model.predict, shap.sample(X,
100))**
或者我们可以使用shap.kmeans()来总结训练数据集:
**explainer = shap.KernelExplainer(rf_model.predict, shap.kmeans(X,
100))**
这里使用 k -means 算法在训练数据集中寻找 100 个聚类,每个聚类的权重与其中的数据点数成正比。结果是 100 个聚类及其相应权重的平均值。现在,这个加权数据集被用作原始数据集的样本来计算 SHAP 值。下面的代码展示了如何在 Python 中实现 k -means 采样。我们首先使用scikit-learn库中的类KMeans将X分成 100 个集群。然后根据每个集群上的数据点数量创建weights数组。最后,聚类的中心和weights数组被传递给kernel_shap2()以生成 SHAP 值。
**kmeans = KMeans(n_clusters=100, random_state=10).fit(X)cluster_size = np.bincount(kmeans.labels_)
weights = cluster_size / np.sum(cluster_size)
X_train_kmeans = kmeans.cluster_centers_
shap_values = kernel_shap2(rf_model.predict, X_train_kmeans,
weights, data_to_explain)**
基于树的模型的 SHAP 值
请记住,我们使用了这个等式:

计算 SHAP 值(参考等式 23)。为了计算上述等式中的条件期望,我们使用等式 29 中给出的近似值:

我们在本节看到,对于基于树的模型(树和树的系综),有一个更好的方法来计算e[f(x)|xₛ。****
正如我们之前提到的,SHAP 是一个模型不可知的解释者,所以要解释的模型是一个黑盒,我们不知道它的类型。但是,在线性 SHAP 或树 SHAP 这样的方法中,我们应该知道模型的类型,所以这些方法不是真正的模型不可知的。然而,这不是一个重要的限制,因为在现实世界的应用中,我们通常知道要解释的模型的类型。
让我们首先在波士顿数据集上尝试一个基于树的模型。
**# Listing 20d = load_boston()
df = pd.DataFrame(d['data'], columns=d['feature_names'])
y = pd.Series(d['target'])
X = df[['AGE', 'RAD', 'TAX', 'DIS']]
tree_model = DecisionTreeRegressor(max_depth=3)
tree_model.fit(X, y)fig = plt.figure(figsize=(20, 10))
plot_tree(tree_model, feature_names=X.columns, fontsize =16)
plt.show()**
在这里,我们使用决策树回归器(来自scikit-learn库)来为具有 4 个特征(AGE、RAD、TAX和DIS)的波士顿数据集的子集建模。我们拟合模型并绘制出结果树,如图 9 所示。

图 9(来源:图片由作者提供)
该树中的每个内部节点都标有数据集的特征,并代表对该特征的测试。该节点的每个分支代表测试的结果。每个叶节点的值给出了通过从树根到叶的路径中的所有测试的特征向量的树模型的预测。例如,我们可以使用这个模型来预测清单 20 中第一行X的目标值:
**X.iloc[0]### Output:
AGE 65.20
RAD 1.00
TAX 296.00
DIS 4.09**
**tree_model.predict(X.iloc[0:1])### Output:
array([23.02767857])**
对于这一行,我们有TAX <=416.5、TAX <= 267.5和RID <= 7.5,所以从树根开始,我们在一个值为 23.028 的叶子中结束(图 10)。该值是第一行X的模型预测。

图 10(来源:图片由作者提供)
现在让我们看看如果我们在特征向量中有一个 NA 值会发生什么。假设我们在X的第一行没有DIS的值。所以特征向量是:

基于这些值,我们可以从根节点到内部节点进行测试RAD<=7.5(图 11),但是我们不能更进一步,因为我们没有RAD的值。然而,通过观察左边和右边的叶子,我们知道在这个模型中,对于这个特征向量,我们只有两个可能的预测。如果RAD<=7.5,那么预测是 23.028。否则预测 30.358。我们还知道在每片叶子中有多少训练数据集的数据样本。这里,248 个样本中有 224 个落在左叶,其余 24 个落在右叶(图 11)。落在每个节点中的样本数被称为该节点的覆盖,因此具有测试RAD<=7.5的节点的覆盖是 248。基于这些结果,我们可以说,得到左节点值的概率是 224/248,得到右节点值的概率是 24/248。因此,该特征向量的模型预测值为:**
********
图 11(来源:图片由作者提供)
这是左边和右边叶子的值的平均值。如果查看图 11,您会注意到测试RAD <=7.5的节点的值是同一个数字(23.737)。这里,scikit-learn计算并显示树的所有内部节点的平均值。让我们看另一个例子。这里的特征向量是:

在根节点,我们不知道TAX的值,所以我们取左右分支的加权平均值。在右边的分支中,我们没有在任何内部节点上测试的特性值,所以我们取所有叶子值的加权平均值:

得到这个值的概率是 166/506。在根的左分支中,我们没有TAX的值,所以我们取测试TAX<=254.5和RAD<=7.5的节点值的加权平均值。对于测试TAX<=254.5的节点,我们取两个叶子的加权平均值。我们知道 RAD 的值,所以对于测试RAD<=7.5的节点,值为 23.028,概率为 248/506。最后,这个特征向量的模型预测是(图 12):
********
图 12(来源:作者图片)
既然我们知道了如何在基于树的模型中处理 NA 特性,我们可以使用算法 1 中的递归算法来计算fₛ(**t13】xₛ)=e**f(x)|xₛ].******

函数EXPVALUE()取待解释的特征向量(*x*)、联盟*S* (包含可用特征)和树模型,计算fₛ(xₛ)≈*ef(x)|x在树形模型中,每个节点都有一个索引,根的索引为 1。*v*是节点值的向量。*vⱼ.type*表示节点是内部节点还是叶节点。向量*a*和*b*表示每个内部节点的左和右节点索引。向量*t*包含每个内部节点的阈值,并且*d*是用于在内部节点中分裂的特征的索引向量。向量*r*包含每个节点的覆盖。*x*.*feature*(*dⱼ*)给出索引为*dⱼ*的*x*中特征的名称。图 13 显示了样本节点的这些向量的值。*****

图 13(来源:作者图片)
函数G()获取节点和累加器的索引。对于一个内部节点,如果它的特征不是S的成员(这意味着我们没有那个特征的值),该函数递归计算左右分支的值,并返回它们的加权平均值。
如果节点的特征在S中,则通过将该特征的值与阈值进行比较来选择左或右分支,并且递归地计算分支的值。最后,对于叶节点,其值乘以叶的覆盖并返回。清单 21 给出了该算法的 Python 实现。
我们可以在图 11 和图 12 所示的联盟上测试这个函数:
**expvalue(tree_model, x=X[:1].T.squeeze(), S=['TAX', 'AGE', 'DIS'])### Output:
23.73709677419353**
**expvalue(tree_model, x=X[:1].T.squeeze(), S=['RAD'])### Output:
22.18510728402032**
如果我们为S尝试一个空列表会发生什么?
**expvalue(tree_model, x=X[:1].T.squeeze(), S=[])### Output:
22.532806324110666**
这是树中所有叶子的加权平均值。实际上,scikit-learn已经将其计算为根节点的值(请看图 9)。这等于训练数据集中所有数据实例的预测平均值。这是因为,对于每个数据实例,模型预测只是其中一片叶子的值,我们知道整个训练数据集的每个值的权重。根据等式 7,当我们没有可用的特征时,模型预测是训练数据集的样本中的实例的预测的平均值,并且这里我们使用整个训练数据集而不是样本。从属性 1 我们还知道ϕ₀=e[f(x)】。所以带着一个空的联盟跑expvalue()返回 ϕ ₀.此外,scikit-learn计算的根节点的值也等于 ϕ ₀.****
现在我们可以用这个函数来计算决策树回归量的 SHAP 值。我们可以使用清单 5 中的相同代码,只是做了一些小的修改。在函数coalition_contribution()中,我们使用expvalue()来计算每个联盟的模型预测,在calculate_exact_tree_shap_values()中,我们使用根节点的值作为 ϕ ₀.
我们现在可以测试这个函数来解释一行X:
**# Listing 23calculate_exact_tree_shap_values(tree_model, X.iloc[470])**
Output:
**(22.532806324110698,
[[0.17363555010556078,
1.6225955204216118,
-6.753886031609969,
1.1484597480832428]])**
算法 1 中定义的函数EXPVALUE()是计算ft27】ₛ(xₛ**)的好得多的选择。现在我们有:****

这使得我们可以为在某个数据集上训练的基于树的模型获得ft35】ₛ(xₛ**)的精确值,并且答案比在****

请记住,我们使用这种近似来计算使用精确和内核 SHAP 方法的 SHAP 值。
算法 1 的时间复杂度与树中的叶子数量成比例。这是因为最坏的情况是当我们有一个不包含树的内部节点的任何特征的联盟时。在这种情况下,算法需要计算所有叶子值的加权平均值。所以EXVALUE()的时间共谋是 O ( L )其中 L 是离开的数字。我们有 M 个特性,对于每个特性,我们必须评估 2 个 ᴹ-1 联盟(不包括那个特性)。因此,找到所有特征的 SHAP 值的时间复杂度为o(lm2ᴹ⁻)=o(lm2ᴹ)****
最后,对于一个集合模型,我们可以在一个集合中使用 T 树,这导致计算所有 M 特征的 SHAP 值的时间复杂度为o(tlm2ᴹ)。结果,对于大值的 M ,使用算法 1 计算基于树的模型的 SHAP 值在计算上是昂贵的。****
树 SHAP 算法
伦德伯格等人。al [3]提出了一种有效的算法,可以降低寻找基于树的模型的 SAHP 值的时间复杂度。可惜[3]中给出的算法错别字太多,没有解释清楚。因此,在这一节中,我将介绍这个算法的一个修正版本,并尝试解释它是如何工作的。算法 2 给出了[3]中原始算法的修正版本(修正用红色标记)。

算法 2
清单 24 给出了该算法的 Python 实现(因为在 Python 中数组和数据帧是零索引的,所以需要做一些修改)。
我们可以用清单 23 中使用的同一行测试tree_shap()函数:
**# Listing 25
tree_shap(tree_model, X.iloc[470])**
Output:
**(22.532806324110698,
array([ 0.17363555, 1.62259552, -6.75388603, 1.14845975]))**
这里我们得到了与清单 23 相同的 SHAP 值。
树 SHAP 算法是如何工作的?(可选 )**
为了理解这个算法,我们首先需要理解它背后的数学[4]。这里我们使用算法 1 的相同符号,但是我们还需要引入一些额外的符号(图 14)。假设我们有一棵树有 L 片叶子,这些叶子的值是 v₁… v_L 。路径 Pₖ 被定义为从根开始到叶子结束的内部节点的集合,叶子的值为 vₖ (我们只包括内部节点,所以叶子本身不包括在路径中)。因此,我们在这个树中有 L 条路径( P ₁… P_L )。
设 j 为路径 Pₖ 中的第 j 个内部节点(因此对于每条路径 Pₖ , j 对于根节点为 0,对于路径中的下一个节点加 1), 并设dₖ= {dⱼ|jϵpₖ}为路径 Pₖ 中内部节点用于拆分的唯一特征的索引集合(这里我们假设特征向量 x 中的每个特征都有一个索引)。 另外, tₖⱼ 表示路径 Pₖ 中内部节点 j 的阈值。因此{tₖⱼ|jϵpₖ}是路径 Pₖ 中内部节点的阈值的集合。我们假设 Tₖⱼ = (-∞, tₖⱼ )如果节点 j 连接到其在路径 Pₖ 中的左子节点,并且 Tₖⱼ = ( tₖⱼ ,∞)如果节点 j 连接到其在路径 Pₖ 中的右子节点。最后,沿着路径 Pₖ 的覆盖率由集合{rₖⱼ|jϵpₖ}表示,其中rₖⱼ=r _(k, j +1)/ rₖⱼ****

图 14(来源:作者图片)
现在可以看出,值 ϕᵢ 可以使用以下公式计算:

𝟙{.在哪里} 是指标函数,所以

这个方程的证明在[4]中给出。
等式 48 中的第一个和是在节点包含特征 i 的所有路径上。如前所述, D ₖ 是路径 Pₖ 中所有独特特征的集合。这个等式中的第三个和是在不包含特征 i 的dₖ与 m 元素的所有联盟之上。因此,对于每个特征 i ,我们需要找到包含该特征的路径,并计算每个路径对该特征的 SHAP 值的贡献。基于等式 48,SHAP 值是所有这些贡献的总和:**

现在让我们看看树 SHAP 算法是如何工作的。算法 2 递归查找树中的所有路径。在每条路径的末端,它计算该路径对所有特征的贡献,并将它们添加到变量 ϕᵢ (因此算法 2 中的每个 ϕᵢ 是特征 i 的形状值的累加器)。当算法覆盖所有路径时, ϕᵢ 等于特征 i 的 SHAP 值。算法 2 中的变量*m*(相当于清单 24 中的数据帧*m*)是一个包含 4 个字段的表:*d*、*z*、*o*和*w*。这些字段保存了我们在每条路径中计算其对特征 i 的 SHAP 值的贡献所需的信息。当算法沿着一条路径行进时,*m*包含了到目前为止在该路径中发现的所有独特特征的索引。这些索引存储在*m*的字段*d*中。在函数RECURSE()中,如果我们找到一片叶子,那么我们使用这个循环:

这里*w* = *sum*(UNWIND(*m*, *i*).*w*)等于:

表中的*mᵢ.o*给出了*m*的值:

而*mᵢ.z* 给出了:

所以计算*w*(*mᵢ.o-mᵢ.z*)*.vⱼ*相当于计算:

在等式 48 中。因此,For 循环会计算路径对该路径中存在的要素的 SHAP 值的影响。For循环从 2 开始,因为我们从那个索引开始填充*m*。函数RECURSE(*j*, *m*, *pz* , *po*, *pᵢ*)有五个参数。*j*是树中当前节点的索引(我们用 0 初始化,0 是根节点的索引)。用于沿路径分割前一个节点的特征索引存储在*pᵢ*中。用于分割当前节点的特征是*vⱼ*。基于这个特性的值,应该选择左边或右边的子节点。所选节点的索引称为热索引(*h*),另一个子节点的索引将是冷索引(c):

由于我们只需要路径中的唯一特征集,算法检查*vⱼ*是否已经存储在*m*中?如果是重复特征,将使用UNWIND()删除。函数RECURSE()在热索引和冷索引结束时调用自身:
********
如果*dⱼ*是一个重复特征,则在*m*(字段mₖ*.z*)中找到它的覆盖比例,并存储在*iz*中。否则,我们有*iz* =1。然后将当前节点的覆盖率(或者是*r*ₕ/*rⱼ*或者是*r*𝒸/*rⱼ*)乘以*i*𝓏并作为*p*𝓏传递给RECURSE()。于是,*p*𝓏 累积起来

沿着小路。保持对…的追踪

对于热索引,我们将*m*ₖ*.o*(如果*dⱼ*是重复特征)或 1 作为*p*ₒ传递给RECURSE(),对于冷索引,我们将 0 作为*p*ₒ传递。调用函数EXTEND()来存储沿路径找到的每个特征的信息。它分别存储了*mᵢ.d*、、、、、、*mᵢ.o*、、中的*pᵢ*、、、*p*ₒ。它还计算****

并将其存储在*mᵢ.w*中。
但是这些值是如何沿着路径产生的呢?函数EXTEND()负责生成以下值

对于 h =0,1,…,|dₖ|。在叶节点处,*m*中的字段*w*包含等式 50 的值,用于 h =0,1,…,|**|dₖ|。请注意,等式 48 中第三个和超过了s⊆dₖ\ {I},|s| =h。因此,为了计算路径对 SHAP 值的贡献,首先我们需要从等式 50 中移除特征{ i }。这就是为什么我们首先调用UNWIND(*m*, *i*).*w*),然后将*SUM*()应用于结果。如前所述*sum*(UNWIND(*m*, *i*).*w*)等于方程式 49。还请注意,EXTEND()和UNWIND()在更改和返回之前都会复制一份*m*。复制的*m*将用于当前节点,而原始的*m*对于前一个节点保持不变。因此,当我们沿着树行进时,*m*仅包含沿着当前路径的独特特征的集合。**
算法 2 将算法 1 的时间复杂度从指数降低到低阶多项式。函数EXTEND()和UNWIND()中的循环都以*m*的长度为界。因为*m*沿着一条路径跟踪独特的特征,所以它受到树的最大深度 D、的限制。因此,UNWIND()和EXTEND()的时间复杂度为 O ( D )。函数RECURSE()中的循环也受到*m*长度的限制。然而,对于一个叶子,函数UNWIND()在那个循环中被调用,所以RECURSE()的时间复杂度是 O ( D ),因为UNWIND()嵌套在由 D 限定的循环中。函数RECURSE()要找到树的所有路径,路径数等于叶子数。所以假设 L 是任意一棵树的最大叶子数,那么RECURSE()对于整棵树的时间复杂度将是 O ( LD )。最后,对于一个 T 树的系综,时间复杂度变为 O ( TLD )。
用于树形整体的树形 SHAP
我们可以很容易地使用tree_shap()函数来计算树集合模型的 SHAP 值。假设我们有一组 T42 树。我们可以分别计算每棵树的 SHAP 值,我们将第 j 棵树 ϕᵢ ⁽ ʲ ⁾.中的特征 i 的 SHAP 值因为所有这些树都同样重要,所以整个集合的特征 i 的 SHAP 值就是集合内所有树的 i 的 SHAP 值的平均值:

清单 26 中的函数tree_shap_ensemble()可以用来计算一个树集合模型的 SHAP 值。
该函数使用树 SHAP 算法来计算集合中每棵树的 SHAP 值,然后取这些值的平均值。我们现在可以在一个随机森林模型上测试这个函数:
*# Listing 27d = load_boston()
df = pd.DataFrame(d['data'], columns=d['feature_names'])
y = pd.Series(d['target'])
X = df[['AGE', 'RAD', 'TAX', 'DIS']]
rf_model2 = RandomForestRegressor(random_state=0, n_estimators=4).fit(X, y)*
*tree_shap_ensemble(rf_model2, X.iloc[470])### Output:
(22.340563241106732,
array([-0.60018849, 1.36914776, -6.2207912 , 2.33626868]))*
SHAP 图书馆里的树 SHAP
SHAP 库中的类TreeExplainer可用于使用树 SHAP 方法为树和树集合计算 SHAP 值。这里我们使用这个类来计算清单 20 中定义的数据集和模型的 SHAP 值。
*explainer = shap.TreeExplainer(tree_model)
shap_values = explainer.shap_values(X.iloc[470])
shap_values### Output:
array([ 0.17363555, 1.62259552, -6.75388603, 1.14845975])*
*explainer.expected_value### Output:
array([22.53280632])*
如您所见,SHAP 值几乎与清单 23 中的 c alculate_shap_values()和清单 25 中的tree_shap()返回的 SHAP 值相同。这个类也可以用于清单 27 中定义的随机森林,其输出几乎与该清单的输出相同。
*explainer = shap.TreeExplainer(rf_model2)
shap_values = explainer.shap_values(X.iloc[470])
shap_values### Output:
array([-0.60018849, 1.36914776, -6.2207912 , 2.33626868])*
*explainer.expected_value### Output:
array([22.34056324])*
SHAP 地块
Shap 库有一些很好的工具来可视化 SHAP 值。瀑布图可以显示各个特征向量的 SHAP 值。作为一个例子,我们在波士顿数据集上训练一个 XGBoost 模型,并显示一个X实例的瀑布图:**
*d = load_boston()
df = pd.DataFrame(d['data'], columns=d['feature_names'])
X = df
y = pd.Series(d['target'])xgb_model = xgboost.XGBRegressor(random_state=1).fit(X, y)explainer = shap.Explainer(xgb_model, X)
explanation_object = explainer(X)
shap_values = explainer.shap_values(X)# visualize the first prediction's explanation
shap.plots.waterfall(explanation_object[0])*

图 15(来源:作者图片)
请记住,根据等式 22,我们有:

瀑布图可以让我们看到这个等式。瀑布图的底部从E[f(x)]= 22.343 开始,图中的每个箭头表示每个特征对预测的正(红色)或负(蓝色)贡献。每个箭头的长度等于其对应要素的绝对 SHAP 值。SHAP 值写在箭头上,相应特征的值也写在纵轴上(例如在上面的图中,特征LSTAT的值是 4.98,其 SHAP 值是 4.64)。如果 SHAP 值为正,箭头为红色并向右。对于负的 SHAP 值,它是蓝色的并向左移动。顺着这些箭头,我们最终得出模型预测的值f(x)= 21.019。要素按其 SHAP 值的绝对值排序,因此具有最大绝对 SHAP 值的要素位于顶部。请注意,我们需要将解释器对象直接传递给waterfall(),而不是由Explainer的shap_values()方法返回的 SHAP 值。**
我们也可以使用力图来可视化 SHAP 值:**
*shap.initjs()
shap.plots.force(explanation_object[0])*

图 16(来源:作者图片)
力图类似于瀑布图,但是箭头是水平堆叠的。红色箭头和箭头根据它们的长度排序,因此红色箭头从左到右变长,蓝色箭头从右到左变长。每个要素的值(不是其 SHAP 值)写在其对应的箭头后面。最长的红蓝箭头相交于f(x)= 21.019。E[f(x)]= 22.343 的值也显示在标为基值的图中。**
我们还可以使用条形图来显示 SHAP 值。我们可以使用接受解释对象的函数shap.plots.bar()。条形图没有显示箭头、f(x)和E[f(x)]的值以及特性的值。每个条形的长度等于其对应要素的绝对 SHAP 值。对于正/负 SHAP 值,该条被涂成红/蓝色,并且 SHAP 值被写在每个条的旁边。要素按其 SHAP 值的绝对值排序。**
*shap.plots.bar(explanation_object[0])*

图 17(来源:作者图片)
我们还可以强制条形图仅显示绝对 SHAP 值:
*shap.plots.bar(explanation_object[0].abs)*

图 18(来源:作者图片)
蜂群图有助于总结大量实例的 SHAP 分析。首先,我们只在一个实例中使用它。函数shap.plots.beeswarm()接受一个Explanation对象。请注意,函数有一个 bug ,当你调用它时,它会改变它所取的Explanation对象的 SHAP 值,所以你应该总是传递一个Explanation对象的深层副本给它。**
*import copy
shap.plots.beeswarm(copy.deepcopy(explanation_object[0:1]))*

图 19(来源:作者图片)
该图类似于条形图,但是,我们用点来表示每个 SHAP 值,而不是条形。每个点的 x 位置给出了相应特征的 SHAP 值。因为我们只有一个实例,所以所有的点都有相同的颜色。现在我们尝试两种情况。现在,对于每个特性,我们有两个点,每个点代表一个实例。
*shap.plots.beeswarm(copy.deepcopy(explanation_object[0:2]))*

图 20(来源:作者图片)
这些点根据相应要素的值进行着色。对于每个特性,具有较高值的实例是红色的,另一个是蓝色的。但是,当我们有多个实例时,这些特性是如何排序的呢?基于所有实例的绝对 SHAP 值的平均值对特征进行排序。现在,我们可以在更多的实例上进行尝试:
*shap.plots.beeswarm(copy.deepcopy(explanation_object))*

图 21(来源:图片由作者提供)
在这个图中,最大 SHAP 值属于特征DIS,但是LSTAT具有所有实例的最高平均值。我们可以改变这一点,并根据其最大绝对 SHAP 值对要素进行排序。
*shap.plots.beeswarm(copy.deepcopy(explanation_object),
order=explanation_object.abs.max(0))*

图 22(来源:作者图片)
现在DIS坐在上面。我们也可以在条形图中使用多个实例。在这种情况下,它将计算所有实例的绝对 SHAP 值的平均值,并基于此对要素进行排序。绝对 SHAP 值的平均值也显示在条形旁边。
*shap.plots.bar(explanation_object)*

图 23(来源:作者图片)
最后,我们可以显示多个实例的力图。以下是 3 个实例的示例:
*shap.force_plot(explainer.expected_value, shap_values[0:3,:],
X.iloc[0:3,:], plot_cmap="DrDb")*

图 24(来源:作者图片)
该图由 3 个单独的力图(如公式 14 中的图)组合而成,它们旋转 90 度并水平堆叠(参见图 16)。下面是另一个有更多实例的例子:
*shap.force_plot(explainer.expected_value, shap_values, X,
plot_cmap="DrDb")*

图 25(来源:作者图片)
我们可以选择单个力图的堆叠方式。例如,在图 25 中,各个图根据相似性进行堆叠。如果我们假设每个实例的 SHAP 值形成了一个 M 维空间中的一个点,那么相似度由这些点之间的欧几里德距离决定,更相似(距离更小)的点(或实例)被堆叠在一起。还有一些其他选项来堆叠实例。例如,我们可以根据每个实例的 f ( x )的值对它们进行排序。这如图 26 所示。这里,实例的f(x)的值从左到右递减。**

图 26(来源:作者图片)
解释机器学习模型是一个重要的话题。解释的意思是,我们希望对模型的单个预测和用于生成该预测的特征之间的关系有一个定性的理解。我们可以用一个简单的、可解释的模型解释器来解释一个复杂的模型,并确定它的特性重要性。SHAP 是一个个性化的模型不可知的解释者,它是基于一个名为 Shapley 值的博弈论概念开发的。SHAP 值提供了线性模型的系数,该模型原则上可以解释任何机器学习模型。
SHAP 值具有一些理想的理论属性,但是,在实践中,计算精确的 SHAP 值在计算上是昂贵的,所以我们使用一些方法,如核 SHAP 来近似 SHAP 值。在本文中,我们首先解释了 Shapley 值的数学概念,以及它们如何用于解释机器学习模型。我们还讨论了 SHAP 值和可以用来估计它们的不同算法。所有这些算法都是在 Pythom 中从头开始实现的。最后,我们讨论了 Python 中的 SHAP 库及其提供的可视化 SHAP 值的绘图工具。
我希望你喜欢阅读这篇文章。如果您有任何问题或建议,请告诉我。本文中的所有代码清单都可以从 GitHub 下载,网址是:https://github.com/reza-bagheri/SHAP
参考文献
1-Scott M Lundberg 和 Su-In Lee。解释模型预测的统一方法。《第 31 届神经信息处理系统国际会议论文集》,第 4768–4777 页,2017 年。
2-斯科特·M·伦德伯格和苏英·李。解释模型预测的统一方法。《第 31 届神经信息处理系统国际会议论文集》(2017),补充材料(https://papers . nips . cc/paper/2017/file/8 a20 a 8621978632d 76 c 43 DFD 28 b 67767-supplemental . zip)。
3-Scott M Lundberg、Gabriel G Erion 和 Su-In Lee。树集成的一致个性化特征属性。 arXiv 预印本 arXiv:1802.03888 ,2018。
4-吉磊杨。快速树型:加速树的 SHAP 值计算。 arXiv 预印本 arXiv:2109.09847 ,2021。
附录
线性 SHAP 方程的证明
假设我们的预测模型 f 是一个线性回归模型:

其中 x ₀=1,特征 Xᵢ 、 i = 1、…、 M 相互独立。使用等式 28,我们可以写出:

假设集合中有 k 特征。因为所有这些功能都是独立的,所以我们有:**

通过替换上一个等式,我们得到:

由于所有的概率都是归一化的,我们可以写出:

此外,我们还有:

所以,我们有:

现在要计算f _s∩{I}(x _s∩{I})从fₛ(xₛ,我们需要取特征 i****

将该等式代入等式 11 并使用等式 4,我们得到:

请注意,在等式 51 中,我们只能依靠训练数据集的样本来计算平均值,因此我们有:

这里 E [ xᵢ ]是 xᵢ 在训练数据集样本的所有实例( k 实例)上的平均值,我们有:

核 SHAP 定理的证明(定理 1)
我们假设我们有一个特征向量 x 和 M 个特征。我们首先在没有缺失特征的情况下证明这一点,因此我们有:

然后我们计算 x 的所有摄动。x’的每一个扰动都是一个有 M 个元素的联合向量,每个元素可以是 0 也可以是 1。所以我们有两个ᴹ微扰。我们把每一个摄动称为【z’【ᵢ】(这是一个行向量)。我们将所有这些扰动(或联合向量)放入 2 个ᴹ× M 个联合矩阵中:**

对于每个联盟向量z’ᵢ,我们可以计算出模型预测fₓ(z【ᵢ】。我们将列向量 y 定义为:****

其中c₀t101】是常数。在介绍 SHAP 1的原文中,c₀t105】不包含在yt109】中,这是不正确的。我们假设****

而我们将 2 ᴹ× 2 ᴹ 对角矩阵t115】wt117】定义为:****

现在假设


我们需要证明当 M 趋于无穷大时

是

其中 ϕᵢ 为等式 18 中定义的 f 和x的 Shapley 值。将等式 57 和等式 58 代入等式 59,我们得到:**

因此,要最小化的目标函数是:

我们可以把联合向量【z’【ᵢ】代入方程 60 得到:**

我们将对最优函数 g 的搜索限制为具有以下形式的线性函数:

这里的₀是等式 54 中使用的相同常数。对于每个联盟向量 z ᵢ ,这个等式写成:

其中[z'ᵢ**ⱼ是联合向量z'ᵢ的第 j 个元素。将该等式代入等式 61,我们得到:****

这里我们用了这样一个事实:等式 53 中[z'ᵢ**ⱼ是矩阵 X 的 ij 元素,等式 54 中 yᵢ 是向量 y 的第 i 元素。现在我们将列向量 c 定义为:**

所以,我们可以用矩阵乘法的定义来写:

请注意, Xc 是一个列向量,【xcᵢ是它的 i -th 元素。我们还知道πₓ(z'ᵢ)是矩阵 W 的第I*-个对角元素(方程式 56)。所以,我们可以再次用矩阵乘法的定义来写:*****

因此,我们得出结论:

这里我们利用了 g 只是 cᵢ 的一个函数这一事实,所以我们不是去找最小化目标函数的函数 g ,而是去找最小化它的向量 c 的值。实际上,这个目标函数类似于加权线性回归模型的损失函数。现在我们可以最小化这个目标函数:

所以,使目标函数最小化的 c 的值为:

现在我们需要简化这个等式。首先我们简化一下xᵀw:**

所以,我们有:

从等式 64 我们可以得出,第 i 行的xᵀw为:**

X 的第 j 列为:

现在根据矩阵乘法的定义,xᵀwx的 ij 元素等于xw和 j 的第行的点积******

现在我们来看看πₓ(z'ᵢ):******

如你所见,它是 M 和z'(|z' |)中非零条目数的函数。所以,对于同一个 x :

另外,我们知道|z|的最大值是 M 。在等式 66 中,如果 i = j ,那么我们有:

由于z'ᵢ是二元向量,【z'ₖ**ᵢ不是零就是一。所以,我们只需要包括[z'ₖ**ᵢ= 1 的术语。现在在这个等式中,我们可以对所有的被加数进行分组,其中|z'ᵢ|是某个数比如说 m 。对于每组πₓ(z'ᵢ)是相同的(只有 m 的功能)😗***

需要注意的是,对于 m =0(当z'ᵢ的所有元素都是 0)和 m = M (当z'ᵢ的所有元素都是 1)的情况下,但是,不影响这个证明。对于 m 的每个值,我们统计|z'ᵢ| = m 和[z'ₖ】ᵢ*= 1 的项数,我们将此计数表示为:*****

所有这些术语都有相同的πₓ(z'ᵢ)。因此,等式 67 可以写成:**

类似的,如果I≦j,我们有:**

现在我们需要计算

我们知道联盟矢量 z 'ₖ 有 M 元素。我们希望第 i 个元素为 1。而z中 1 的总数应该是 m 。所以除了第 i -th 元素,我们需要的 m -1 个元素为 1,其余为 0。所以,这就像从 M -1 个元素中选择 m -1 个元素。所以:

同样,我们有:

将这些等式与等式 68、等式 69 和等式 70 相结合,我们得到:

基于这个结果,xᵀwx的所有对角元素等于 E 1,所有非对角元素等于 E 2。所以,我们可以把xᵀwx写成:**

其中 I 为 M × M 单位矩阵, J 为一个 M × M 的矩阵,其中每个元素都等于 1。现在我们需要计算这个等式中的 a 和 b。对于xᵀwx的非对角元素我们有:**

对于对角线元素:

因此,我们有一个由两个方程组成的系统,通过求解它们,我们得到:

对于 m = M ,等式右边的最后两项相互抵消(尽管它们都趋向于无穷大):

我们现在可以简化这个等式:

将该等式代入等式 71,我们得到:

现在我们要计算(xᵀwx)⁻。我们可以证明:**

为了证明我们应该记住,对于任何矩阵 一 :

现在我们有:

对于任何 M × M 矩阵像 J 我们可以很容易地表明:

将这个等式代入上一个等式,我们得到:

所以随着 M 趋于无穷大,这个等式的右边就变成了 I 。同样,我们可以证明:

在介绍 SHAP [2]的原始论文中,xᵀwx推导不当,然而(xᵀwx))⁻仍然正确!**
现在我们需要计算(xᵀwx)⁻xᵀw)。从等式 65 我们知道j-第列的xw是πₓ(zz的 ij 元素(xᵀwx)⁻xᵀw是我的内积 -th 从等式 73 中,我们知道********

其中下标 i ,表示矩阵的第 i 行。最后我们可以从方程 63 计算出矢量 c 。向量 c 的第 j 个元素是矩阵第 j 行(xᵀwx)⁻x*******

我们先考虑一个联盟向量z'ᵢ对于其中的z'ᵢ**ⱼ= 0。对于这样一个矢量,我们可以使用等式 55 和等式 74,并写出:**

然后我们考虑一个联合向量z*'ᵢ₊它等于 z 'ᵢ ,其中 j -th 元素设置为 1。所以,我们可以写:*****

我们还假设:

现在我们可以用等式 74 和等式 77 来写:

结合等式 76 和等式 78,我们得到:

最后,我们可以用等式 75 来写:

与其求和超过**'ᵢfor which[z'ᵢ**ⱼ= 0,我们可以求和超过z*'ᵢ₊。在这种情况下,使用等式 77,我们得到:*****

记住对于z'ᵢ₊第 j 个元素设置为 1。但是,我们也可以添加任何 z ' ᵢ 其中[z'ᵢ**ⱼ= 0。那是因为对于这样一个联军向量,fₓ(z'ᵢ₊)-fₓ(z*'ᵢ)*****

如果我们将该等式与等式 18 进行比较,我们得出结论:

于是,向量 c 给出了 f 和 x 的 Shapley 值,使等式 61 中的目标函数最小化的函数具有如下形式:

一般可以写成:

如你所见,最小化并不能决定₀.的值(这就像最小化一个函数,比如y=x+c,其中无论 c 的值是多少,最小点都在 x =0)。为了确定 c ₀,我们将所有特征设置为 NA ,然后 z ' ᵢ =0 对于 i =1.. M 。因此,我们有:****

我们还知道,在这种情况下,模型预测是训练数据集样本中实例预测的平均值(等式 22)。所以:

因此,当 M 趋于无穷大时,我们有:

其中ϕ₀=e[f(x)】和 ϕᵢ (对于 i =1.. M 给出 f 和 x 的沙普利值。为了这个证明,我们最初假设|x' | =M,所以 x '没有任何零元素, x 没有缺失特征。如果我们在x'(|x' |<m)中有一些零元素,我们只包括 x '的元素,它们是 1 ( x ' ᵢ =1),同样的证明也适用于它们。对于x’(对应于 x 中缺失的特征)的零元素,我们假设 ϕᵢ =0,这与等式 18 一致。****
核 SHAP 解的简化方程
考虑等式 35 中定义的联盟矩阵 X ,设 z '₁和z' _ 2ᴹ分别为全零和全一联盟。我们首先通过从联合矩阵×x中消除'₁和z’_ 2ᴹ来创建一个名为**×t83】的新矩阵:********

如果我们在等式 40 的目标函数中消除对应于 z '₁和z' _ 2ᴹ的项,我们得到:****

现在我们可以使用等式 43 得到:

请注意[z'ᵢ]_m是矩阵xₜ的第m-列。现在我们可以将(2ᴹ-2】×(m-1)矩阵xᵣ定义为:****

这里[xₜ_ ,I 是 X t 的第I-列。所以 X ᵣ 是从第 M -1 列减去最后一列 X ₜ 而成。现在你可以很容易地表明,的 ij 元素是:***

我们还将列向量 c ᵣ 定义为:

并将列向量 y ᵣ 与 2 个 ᴹ -2 元素定义为:

所以,**【ᵣ的第I-t 列为:****

现在使用矩阵乘法的定义,我们有:

最后,我们将(2ᴹ-2)×(2ᴹ-2)对角矩阵 W ᵣ 定义为:****

现在,如果我们遵循用于简化等式 62 的相同程序,我们会得到:

所以,我们需要解决这个最小化问题:

与方程 63 相似,使目标函数最小化的 c的值为:

基于关联规则挖掘的简单购物篮分析
通过概念、关联规则挖掘的工作机制和 Python 代码理解客户行为

介绍
你一定已经注意到比萨饼店的售货员做了比萨饼、软饮料和薯条的组合。他也给购买这些套餐的顾客打折。你有没有想过他为什么要这么做?
因为他发现买披萨的顾客也会买软饮料和薯条。通过做组合,他让顾客很容易。同时,他也提高了自己的销售业绩。
从上面的小例子来看,收集和分析用户行为对于在销售中创建有效的交叉销售和追加销售策略的重要性是不可否认的。基于用户行为的数据,我们可以使用 RFM 和聚类分析将客户分成不同的组,以便为每个组提供定制的服务。但是,要预测客户可能购买的其他产品,分析师经常使用的了解客户购买习惯的方法之一是 关联规则挖掘 。
在今天的文章中,我将向您提供关于这项技术的最全面的信息,以及如何使用它来更好地了解客户。
什么是关联规则挖掘?
基于交易历史,可以利用许多模式来理解客户行为。可以实现的洞察之一是所购买的物品之间的关联。使用所有购买商品的数据,我们可以确定哪些商品通常是一起购买的,从而定义和建立产品关联模型。结论必须进行概率和可靠性测试。例如,一个人去超市买面包。他/她有 80%的可能性购买果酱或火腿与面包一起吃,有 90%的把握。

作者图片
然后,零售商可以使用分析结果来定位、推荐和导航商店中的产品,以促进产品或产品类别之间的交叉销售。
基于 Apriori 的关联规则挖掘
Apriori 是一种算法,它利用数据中相互关联的对象集。换句话说,它有助于识别频繁出现的项目集。先说一个最小发生阈值为 1 的 apriori。该过程从查找满足最小出现要求的单个对象开始。然后,它一次一个项目地扩展项目集,检查更广泛的项目集是否仍然满足预定义的阈值。当不再有满足最小出现条件的项目要添加时,算法终止。
先验的成分
假设我们有 4 个订单,每个订单描述如下:

作者图片
在这 4 个订单中,我们有 3 个包含苹果的订单,3 个包含香蕉的订单,2 个包含香蕉和苹果的订单。利用这些信息,我将计算一些重要的先验指标,包括支持度、信心度和提升度。
支持
这是包含项目集的订单比例。正如您在上面的示例中所看到的,3 个订单包括总共 4 个订单中的香蕉,因此:
***Support (bananas)
= Bananas related orders/Total orders
= 3/4 = 0.75***
信心
在这个例子中,置信度表达了在购买香蕉之后购买苹果的次数的百分比。所以,公式可以如下所示:
***Confidence (bananas -> apples)
= support (bananas, apples) / support (bananas)
=(2/4)/(3/4) = 0.6667***
66.67%的值意味着购买香蕉的顾客中有 66.67%也购买苹果。然而,这个数字是否暗示了这两种水果之间的关系,或者它们仅仅是偶然一起购买的?为了回答这个问题,我们将不得不看看另一个衡量这两种事物受欢迎程度的指标。
根据业务目的、研究背景和行业领域,支持度和置信度阈值的设置是不同的。例如,在分析金融欺诈时,支持值可能是 1%,因为数据集中出现欺诈的交易相对较少。因此,没有特定的标准来决定一个特定的阈值。
电梯
此指标显示商品之间是否实际存在关联,或者购买这些商品是否只是随机组合。从这个例子中,我们可以快速计算升力:
***Lift(bananas, apples) = Lift(apples, bananas)
= support(bananas, apples) / (support(bananas) * support(apples))
=(2/4)/(0.75*0.75) = 0.89***
因此,该度量意味着,如果香蕉出现在 75%的订单中,苹果出现在 75%的订单中,并且这两个项目之间没有联系,则苹果和香蕉预计会在 75% * 75% = 56.25%的情况下一起购买。同时,分子反映了苹果和香蕉以相同顺序出现的频率。在这个场景中,这是 50%的时间。得到这个值并除以 56.25%显示了苹果和香蕉以相同的顺序出现的次数比它们没有关联时多多少次。
基于升力值的结果,我们可以有一些推论:
- lift = 1:物品随机一起买。换句话说,项目之间没有关系。
- lift > 1:物品一起购买的频率高于随机购买。
- 抬起< 1: items are bought together less regularly than random.
In the example above, apples and bananas appear together 0.89 times more than random. Therefore, we can tell there is a negative relationship between these 2 items.
Now, let’s see how the association rules mining works in a real dataset.
Dataset
For this article, I will use a dataset from a bakery (License: CC0: Public Domain). This dataset includes 20507 entries, over 9000 transactions, and 5 columns. You can download the data from this 连杆。
数据集的概述如下所示:

数据类型-按作者分类的图像

最后五行—作者图片
从 2016 年 11 月到 2017 年 3 月,总交易量急剧增加,如下图所示。

每月交易—按作者分类的图像
下表描述了销售额最高的 10 件商品。咖啡、面包和茶是面包店最受欢迎的商品。

现在,让我们看看如何将关联规则挖掘应用于这些数据。
转换数据集
因为事务中的项目被拆分到不同的行中,所以我将这些项目分组到一个地方。项目列表的列表转换如下:

Apriori 模块需要一个值为 0 和 1 或 True 和 False 的数据帧。因此,我将使用一个热编码数据,以满足 mlxtend 库给出的 Apriori 模块的要求。

一次热编码后—作者提供的图片
在应用 Apriori 模块之前,我们需要导入一些必要的库:
现在,我们可以轻松地应用 mlxtend 库中的 apriori 模块。只用一行代码就可以找到频繁集。在这个例子中,我将使用min_support = 0.05,它意味着选择一个项目集所需的最小支持。同时,use_colnames = True为项目集保留列名,使它们更容易理解。

从这些频繁集中,我继续寻找决定T5 的关联规则,如果 A 被购买, 那么 B 也被购买。我将metric = 'lift'设置为最小阈值= 1。

关联规则-按作者分类的图像
正如我们所看到的,结果只包含两个关联规则。在lift = 1.1和 53%的置信度下,蛋糕和咖啡比随机购买更频繁。

作者图片
限制
尽管 Apriori 算法易于应用,但它仍有某些限制,包括:
- 如果事务数量很大并且内存容量有限,则算法的效率会降低。
- 由于它需要扫描整个数据库,因此需要很高的处理能力
结论
了解关联规则挖掘技术将有助于更好地了解客户,从而有合适的策略来促进销售。可以从该方法中提取的一些益处可以提及如下:
- 展位安排:您可以将相关商品组合在一起。基于消费者购买习惯的组合商品价格。
- 产品推荐:产品推荐要基于用户购买习惯。如果顾客购买更多的组合,你可以给他们折扣。或者,客户可以用最低总价购买额外的商品。
- 库存管理:市场篮子分析成为预测未来采购的基础。此外,销售数据是同时获取的,因此保持产品供应和控制库存变得更加高效。
- 优化广告成本:充分利用你的广告机会。客户对信息、沟通和产品的反应反映了这一点。
我希望我的文章能为那些想更多了解这种有用方法的人更好地理解客户行为打下良好的基础。
参考
https://www.kaggle.com/code/datatheque/association-rules-mining-market-basket-analysis
SimPy 仿真简介
原文:https://towardsdatascience.com/introduction-to-simulation-with-simpy-322606d4ba0c
第二部分:排队系统的性能测量

在之前的文章中,我们提供了关于离散事件模拟(DES) 的基本概念和原理的概述。我们描述了什么是模拟,什么是模型,什么是 DES,什么是 SimPy。我们介绍了一些在模拟研究中使用的概率和统计的基本概念,以及如何为系统模拟生成概率结果。
在本文中,我们将展示如何使用 SimPy 框架来模拟一个排队系统。为此,我们将描述一些与排队系统相关的基本概念,这些概念是通常用来衡量此类系统性能的量。
排队系统
离散事件模拟技术最常见的应用是排队系统。他们还将等待排队系统命名为 T17。
许多产品和服务行业都有一个排队系统,在这个系统中,实体(顾客、客户,产品)到达一个站点(服务设施),在队列或队列中等待(或不等待),直到他们获得由服务器提供的某种服务,并在结束时离开系统。例如:银行、加油站、卡车称重设施、港口、机场、医疗中心、急诊室、工厂、计算机系统、网络、修理厂和工作人员等。
排队系统(等候线系统)由三部分组成:1)到达过程;2)排队或等候过程;3)服务设施。
1)到达流程是客户到达并请求服务的过程。在现实世界的设施或过程中,到达通常随机发生,并由概率分布函数描述。到达过程最重要的特征是到达之间的时间(两次连续到达之间的时间)。
许多真实世界的系统近似为泊松分布,以描述固定时间段内的到达次数。但是在离散事件模拟技术的实际应用中,使用了以下规则:如果单位时间内的到达次数为泊松,则到达之间的时间遵循连续的指数分布。
与到达过程相关的另一个重要因素是潜在客户的人口。当它的数量大到到达率不随相继顾客的到达而变化时,假定它是无穷大。相反,如果这一比率取决于已经到达并接受服务的人数,则应将其视为有限人口。
2)排队过程的特征在于排队行为、排队规则和等待区域。
排队行为指顾客在等待服务时可以采取的行动。最常见的三种是: balk (队伍太长时离开);骑师(从一条线移到一条更短的线);食言(如果队伍移动缓慢,则离开)。
队列规则与空服务器选择下一个客户机的方式有关。最常见的有:FIFO(先进先出);后进先出法;优先(通常在急诊室);车间调度中最短加工时间优先;网络调度程序中的循环调度。
排队过程的另一个特征与每个队列中可以等待的顾客数量有关。在某些情况下,这个数字相当大,并不意味着任何实际问题,并且等待区域被认为是无限的。相比之下,当有可能由于等候区空间不足而拒绝几个顾客进入队列时,队列的长度应被认为是有限的。
3)服务设施的特征在于服务机制和服务费率。
服务机制显示了不同的配置:单个服务器(柜员、ATM、急诊室的入院服务、CPU、传送带);多个服务器,相同或不同(银行柜员、超市收银员);顺序服务器(垃圾处理系统中的起重机和焚烧炉、制造配置、急诊室)。
服务费率可以是确定性的,也可以是概率性的。如前所述,现实世界的设施有一些由概率分布描述的随机成分。模拟研究中最常用的是表示服务器处理时间的正态分布或表示排队系统中服务时间分布的指数分布(这种假设很少是可信的)。
总服务成本由等待成本和服务成本组成。许多模拟研究中的概念想法是到T6 确定最小化总服务成本的配置。
根据 Kendall 符号对排队模型进行描述和分类。我们会在下面的文章中描述。
排队系统的性能测量
某些数量衡量等待系统的性能。三个最常见的是:1)排队的平均延迟;2)平均排队顾客数;3)服务器忙于服务客户的时间比例。
-
平均排队延迟衡量“普通顾客”在接受服务前应该等待的时间。
-
时间平均排队顾客数显示“平均顾客”到达系统时应该看到的排队长度。
3)服务器忙于服务客户的时间比例称为服务器利用率。它测量服务器繁忙时间的百分比。低值意味着服务能力过剩,因此成本效率低。非常高的值意味着瓶颈的可能性,因此存在客户流失的可能性。
用 SimPy 模拟单服务台排队系统
我们将使用 SimPy 来模拟位于高速公路边的称重站的卡车到达和服务。
该系统的负责人在安装之前,希望确保至少在很长一段时间内,不会有卡车停在路边等待称重。停在公路边的卡车会引起司机的抱怨,并可能引发交通事故。
为此,他们决定聘请一名数据科学家对提议的系统进行模拟研究。
他发现卡车会按照平均 3 分钟的指数分布随机到达。称重站的制造商保证称重过程由平均值为 3 分钟、标准偏差为 0.5 的正态分布表示。三辆以上的卡车停在公路边上是一种危险状况。
获得所需性能指标的代码如下:
首先,他导入了 SimPy,Pandas 作为 pd, Numpy 作为 np ,以及 scipy.stats 模块,该模块包含了再现遵循指数分布和正态分布的随机数序列的方法(norm&expon)。
import simpyimport pandas as pd
import numpy as npfrom scipy.stats import norm
from scipy.stats import expon
接下来,包括一个初始化模块,在该模块中,称重站中存在的秤的数量、队列中可容纳的最大卡车数量、两种分布的参数、模拟结束的时间以及将用于存储中间结果的列表的值被初始化。
# initialization moduleNUMBER_TRUCK_SCALES = 1
NOT_ALLOWED_NUMBER = 4TRUCK_ARRIVAL_MEAN = 3TRUCK_SERVICE_MEAN = 3.0
TRUCK_SERVICE_STD = 0.5SIM_TIME = 100arrivals, departures = [],[]
in_queue, in_system = [],[]
tme_in_queue, len_in_queue = [],[]
模拟算法的核心代码如下。
首先,我们需要创建一个环境实例。Environment()) 在这里我们可以定义资源和进程对象。然后,我们用资源( scales_lines = simpy)定义称重站。Resource(env,capacity = NUMBER _ TRUCK _ SCALES)我们选择一个随机种子来生成卡车到达间隔时间和秤服务时间序列。
卡车是我们的活动部件。它们用两个流程对象建模:1)env . Process(truck _ arrival(env,scales _ lines)); 2) 环境过程(wheighing(env,number_scales,next_truck_id,time_of_arrival)) 。最后,我们运行模拟一段预定的时间(env . run(until = SIM _ TIME))。Yield 语句被编码以建立离散事件调度。
def truck_arrival(env, number_scales): # IDs for trucks
next_truck_id = 0 while True:
## exponential distribution for arrivals
next_truck = expon.rvs(scale = TRUCK_ARRIVAL_MEAN, size = 1) # Wait for the truck
yield env.timeout(next_truck)
time_of_arrival = env.now
arrivals.append(time_of_arrival)
next_truck_id += 1
print('%3d arrives at %.2f' % (next_truck_id, env.now))
env.process(wheighing(env, number_scales,
next_truck_id,time_of_arrival))#..................................................................def wheighing(env, number_scales, truck_number, time_of_arrival): with scales_lines.request() as req: print('%3d enters the queue at %.2f' %
(truck_number, env.now)) queue_in = env.now
length = len(scales_lines.queue)
tme_in_queue.append(queue_in)
len_in_queue.append(length) yield req
print('%3d leaves the queue at %.2f' %
(truck_number, env.now)) queue_out = env.now
length = len(scales_lines.queue)
tme_in_queue.append(queue_out)
len_in_queue.append(length) # normal distribution for the weighing process
r_normal = norm.rvs(loc =TRUCK_SERVICE_MEAN,
scale =TRUCK_SERVICE_STD,size=1) yield env.timeout(r_normal)
print('%3d permanece %.2f' % (truck_number,r_normal)) time_of_departure = env.now
departures.append(time_of_departure) time_in_system = time_of_departure - time_of_arrival
in_system.append(time_in_system) time_in_queue = queue_out - queue_in
in_queue.append(time_in_queue)#.................................................................## set up the environment
env = simpy.Environment()## defining resources
scales_lines = simpy.Resource(env, capacity = NUMBER_TRUCK_SCALES)## selecting a random seed for the probability distributions
RANDOM_SEEDS = [1234, 5678, 9012, 3456, 7890]
np.random.seed(seed= RANDOM_SEEDS[1])## defining the truck arrival process
env.process(truck_arrival(env, scales_lines))## run the simultion
env.run(until = SIM_TIME)
接下来,描述用于计算队列长度的时间加权平均值、服务器利用率和不允许等待的卡车在队列中的时间百分比的例程:
def avg_line(df_length): ## finds the time average number of customers in the waiting line ## use the next row to figure out how long the queue was df_length['delta_time'] = df_length['time'].shift(-1)
- df_length['time'] ## drop the last row because it would have an infinite delta time df_length = df_length[0:-1] avg =np.average(df_length['len'],weights=df_length['delta_time']) return avg#.................................................................def server_utilization(df_length): ## finds the server utilization sum_server_free = df_length[df_length['len']==0] ['delta_time'].sum() ## the process begins with the server empty
first_event = df_length['time'].iloc[0]
sum_server_free = sum_server_free + first_event utilization = round((1 - sum_server_free / SIM_TIME) * 100, 2) return utilization#..................................................................def not_allowed_perc(df_length): ## finds the percentage of time of trucks on queue
not allowed to be waiting sum_not_allowed = df_length[df_length['len']>= NOT_ALLOWED_NUMBER]['delta_time'].sum() not_allowed = round((sum_not_allowed / SIM_TIME) * 100, 2) return not_allowed
将列表转换为数据帧,计算绩效指标并打印:
df1 = pd.DataFrame(tme_in_queue, columns = ['time'])
df2 = pd.DataFrame(len_in_queue, columns = ['len'])
df_length = pd.concat([df1, df2], axis = 1)avg_length = avg_line(df_length)utilization = server_utilization(df_length)not_allowed = not_allowed_perc(df_length)df3 = pd.DataFrame(arrivals, columns = ['arrivals'])
df4 = pd.DataFrame(departures, columns = ['departures'])
df_chart = pd.concat([df3, df4], axis = 1)avg_delay_inqueue = np.mean(in_queue)avg_delay_insyst = np.mean(in_system)print(' ')
print('The average delay in queue is %.2f' % (avg_delay_inqueue))
print('The average delay in system is %.2f' % (avg_delay_insyst))
print('The average number of trucks in queue is %.2f' % (avg_length))
print('The utilization of the server is %.2f' % (utilization))
print('The probability of not allowed trucks in queue is %.2f' % (not_allowed))
最后,一个图表显示了称重站的到达和离开,另一个图表显示了队列中卡车数量的变化:
import plotly.graph_objects as gofig1 = go.Figure()fig1.add_trace(go.Scatter(x = df_chart['arrivals'],
mode = 'markers',name = 'Arrivals'))fig1.add_trace(go.Scatter(x = df_chart['departures'],
mode = 'markers',name = 'Departures'))fig1.update_layout(title = 'Arrivals & Departures
at the Weighing Station',
xaxis_title = 'Time' , yaxis_title = 'Truck ID',
width = 800, height = 600)
fig1.show()fig2 = go.Figure(go.Waterfall(x = df_length['time'],
y = df_length['len'],
measure = ['absolute']* 100,
connector = {"line":{"color":"red"}}))fig2.update_layout(title = 'Number of Trucks in Queue',
xaxis_title = 'Time' , yaxis_title = 'Number of Trucks',
width = 800, height = 600)
fig2.show()
分析
一辆卡车平均要等 5.77 分钟才能得到服务。称重系统的平均延迟为 8.71 分钟。平均来说,大约有 2 辆卡车在排队。服务器的利用率很高,空闲时间很少,没有瓶颈。


作者用 Plotly 制作的图表。
尽管上述所有性能指标都在可接受的范围内,但在系统运行时间的 15.82%中,有危险数量的卡车停在公路边。

作者用 Plotly 制作的图表。
在下一篇文章中,我们将看到提出了哪些替代方案来解决这个问题。使用模拟技术的最大优点是评估所提出的系统,就像它实际存在一样。
别忘了订阅我的电子邮件来接收下面的文章。
SimPy 仿真简介
原文:https://towardsdatascience.com/introduction-to-simulation-with-simpy-5656bb4aafbe
第 6 部分:多项绩效指标

安特·罗泽茨基在 Unsplash 上的照片
如副标题所示,这是与使用 SimPy 的模拟技术应用相关的系列中的第篇,SimPy 是基于 Python 的基于流程、面向对象、离散事件模拟框架。
之前的文章都发表在走向数据科学以下是对应链接:第一篇;第二条;第三条;第四条;第五条。
在第 4 篇文章中,我们指出模拟研究是基于计算机的统计抽样实验1。我们建立现实世界问题的数学或逻辑模型,并对其进行实验,直到获得足够的知识来解决预定义的决策问题。
通常,现实世界的系统在数据中表现出很大的随机性。因此,统计和概率对仿真分析至关重要。
在第 4 篇文章中,我们使用了一个名为固定样本量程序的统计程序来进行终止模拟的输出数据分析。我们计算了置信区间 ( CIs ),因为绩效指标的样本均值总是有一些误差。这种方法的缺点是我们不能确定这些置信区间的大小。
因此,在第 5 篇文章中,我们使用了一个顺序程序,使我们能够获得一个具有预定义水平绝对或相对精度的置信区间。这个程序的缺点与覆盖概率(区间包含真均值的实际概率)有关。分析员必须始终意识到使用顺序程序时在覆盖范围内严重损失的可能性。
在本文中,我们将开发一个同时分析几个性能指标的程序。
多项绩效指标
第 4 条和第 5 条中描述的程序分别说明了达到几个置信度的置信区间所需的运行次数。但是通常的做法是在执行模拟研究时计算多个性能度量。因此,我们需要一个过程来同时为的多个绩效指标达到指定的置信水平。
这种情况在统计文献中被称为多重比较问题。用 Bonferroni 不等式描述:

Bonferroni 不等式用于多重统计推断,以设定总体置信水平的上限。所有同时包含其相应性能指标的配置项必须满足 Bonferroni 不等式的概率。
使用少量测量值进行模拟研究的惯例如下:

这是一个非常保守的程序,但它不需要概率分布假设,并且允许单独定义每个性能测量的置信水平。
例如,有了三个性能指标和总体 90%的置信区间,我们可以在三个 96.667%的置信区间(0.0333+0.0333+0.0333 = 0.1 =α)或一个 95%和两个 97.5%的置信区间(0.05+0.025+0.025 = 0.1 =α)之间进行选择****
让我们用 SimPy 来说明这个过程。
用 SimPy 模拟机器的故障和维修
我们用 SimPy 对一家工厂的机器故障进行编码,并由三名维修人员进行相应的维修。
正在研究的系统是一个排队系统,其中机器是随机发生故障的客户,由三个修理工(服务器)随机花费时间修理。每台机器平均每 10 分钟故障一次,机器故障间隔时间呈指数级。服务时间(以分钟为单位)不遵循已知的概率分布,由下表表示:

下面的 Python 代码允许我们应用一个顺序过程来获得三个性能指标的总体置信度为 90%的。
我们导入了熊猫库作为 pd, Numpy 作为 np , RandomState 模块,模拟框架 SimPy。我们需要 scipy.stats 模块,它包含了再现服从均匀和指数分布的随机数序列的方法。最后,我们导入 matplotlib 作为最终输出的 plt 。
****import pandas as pd
import numpy as np
from numpy.random import RandomStateimport simpyfrom scipy import stats
from scipy.stats import expon
from scipy.stats import uniformimport matplotlib.pyplot as pltyour_path = # type your particular path****
在初始化模块中,我们指出了与机器故障间隔时间和维修人员数量相关的指数分布的参数。我们设置一个数据帧( df_service )用于函数修复以及从均匀分布获得的随机变量,以确定修复时间值。第 3 条中使用了类似的逻辑。**
****# initialization moduleMACHINE_FAILURE_MEAN = 10
NUMBER_REPAIR_PERSONS = 3## times in repairing
list_of_minutes = [15, 30, 40, 50]# discrete probabilities for times in repairing
prob1, prob2, prob3, prob4 = 0.1, 0.3, 0.4, 0.2prob1 = round(prob1, 4)
prob2 = round(prob1 + prob2,4)
prob3 = round(prob2 + prob3,4)
prob4 = round(prob3 + prob4,4)
list_of_probs = [prob1, prob2, prob3, prob4]df1 = pd.DataFrame(list_of_minutes, columns = ['minutes'])
df2 = pd.DataFrame(list_of_probs, columns = ['range'])df_service = pd.concat([df1, df2], axis = 1)****
下面两个生成器函数( def machine_failure(env,number_repair)&def repair(env,number _ repair,failure_number,time_of_failure) 背后的逻辑也在第 3 条中进行了描述。**
****def machine_failure(env, number_repair): # counter of failures
failure_number = 0 while True:
## exponential distribution for failures
next_failure = expon.rvs(scale = MACHINE_FAILURE_MEAN,
size = 1) # Wait for the failure
yield env.timeout(next_failure)
time_of_failure = env.now
failures.append(time_of_failure)
failure_number += 1
print('failure %3d occurs at %.2f' %
(failure_number, env.now)) env.process(repairing(env, number_repair,
failure_number, time_of_failure))#...................................................................
def repairing(env, number_repair, failure_number, time_of_failure): with repair_persons.request() as req:
print('%3d enters the queue at %.2f' %
(failure_number, env.now))
queue_in = env.now
length = len(repair_persons.queue)
tme_in_queue.append(queue_in)
len_in_queue.append(length) yield req
print('%3d leaves the queue at %.2f' %
(failure_number, env.now))
queue_out = env.now
length = len(repair_persons.queue)
tme_in_queue.append(queue_out)
len_in_queue.append(length)
# uniform distribution for the repairing process
r_v = uniform.rvs(size=1)
print(r_v) for i,row in df_service.iterrows():
probab = df_service.loc[i, 'range']
if r_v < probab:
time_service = df_service.loc[i, 'minutes']
break yield env.timeout(time_service)
print('%3d stays at service %.2f' %
(failure_number,time_service)) time_repaired = env.now
repaired.append(time_repaired) time_out_system = time_repaired - time_of_failure
out_system.append(time_out_system) time_in_queue = queue_out - queue_in
in_queue.append(time_in_queue) time_in_service = time_service
in_service.append(time_in_service)
#..................................................................****
接下来,我们编写了两个函数(def avg _ line(df _ length)&def calc _ measures()),用于计算队列中故障机器的平均数量、队列中的平均延迟、机器被修复的平均时间以及机器离开系统的平均时间。****
****def avg_line(df_length): # finds the time weighted average of the queue length
# use the next row to figure out how long the queue was at that length df_length['delta_time'] = df_length['time'].shift(-1)
- df_length['time'] # drop the last row because it would have an infinite time span
df_length = df_length[0:-1]
avg = np.average(df_length['len'],
weights = df_length['delta_time']) return avg
#..................................................................def calc_measures(): df3 = pd.DataFrame(tme_in_queue, columns = ['time'])
df4 = pd.DataFrame(len_in_queue, columns = ['len'])
global df_length
df_length = pd.concat([df3, df4], axis = 1) avg_length = avg_line(df_length)
avg_delay = np.mean(in_queue)
avg_in_service = np.mean(in_service)
avg_out_system = np.mean(out_system) print('Number of Run: %1d' %(run+1))
print('The average delay in queue is %.2f' % (avg_delay))
print('The average number of machines in queue is %.2f' % (avg_length))
print('The average time machines were being repaired is %.2f' % (avg_in_service))
print('The average time machines out of system is %.2f' % (avg_out_system))# list and dataframe for final output listoflists = []
listoflists.append(round(avg_delay,2))
listoflists.append(avg_in_service)
listoflists.append(avg_out_system)
listoflists.append(avg_length)
df.loc[len(df)] = listoflists****
函数 calc_ICs() 允许我们使用文章 5 中描述的公式计算置信区间。我们使用了自由度为k1的 T33的四个不同分位数 ,每个分位数对应一个特定的置信水平。当每个选择的性能测量的置信区间 ( hwic )的半宽度小于先前定义的绝对精度时,我们将布尔变量( l_end )设置为真。******
**def calc_ICs(): ## confidence intervals
## define 3 global variables
global df_output, hwic, l_end mean = round(df.mean(),2)
sigma= round(df.std(ddof=1),2)
dof = len(df)-1 for i in range(4):
t_crit = np.abs(stats.t.ppf((1-confidence[i])/2,dof))
print(round(t_crit,3)) inf, sup = (mean - sigma*t_crit/np.sqrt(len(df)),
mean + sigma*t_crit/np.sqrt(len(df))) inf = round(inf,2)
sup = round(sup,2) df_output = pd.concat([mean, sigma, inf, sup], axis=1)
print(df_output) hwic= (sup - inf)/2
if (hwic[0]<=abs_err[0]) and (hwic[1]<=abs_err[1])
and (hwic[3]<=abs_err[3]):
l_end = True print('')
print(round(hwic,2),abs_err, l_end )**
函数 print_output() 打印输出数据表,显示每个绩效指标的样本均值、标准偏差以及 CIs 的下限和上限。
**def print_output(): col_labels = ["Mean", "Std. Dev.", "Lower bound", "Upper Bound"] row_labels = ["Delay in Queue","In Repair",
"Out of System","Machines in Queue"] fig, ax = plt.subplots(1,1)
ax.axis('tight')
ax.axis('off') output_table = ax.table(cellText = df_output.values,
colLabels = col_labels, rowLabels = row_labels,
rowColours =["skyblue"]*5, colColours =["cyan"]*4,
cellLoc='center', loc="center")
ax.set_title("Output data for %i independent runs" %(run+1),
fontsize=18, y= 0.8 , pad = 4) output_table.auto_set_font_size(False)
output_table.set_fontsize(8)
plt.savefig(your_path +'output_perf_measures.png',
bbox_inches='tight', dpi=150) plt.show()**
最后,我们基于三个 96.667%的 IC(0.0333+0.0333+0.0333 = 0.1),以及队列中平均延迟为 3.0、机器被修理的平均时间为 2.0 以及队列中故障机器的平均数量为 2.0 的绝对精度( abs_err ,为模拟算法的中央核心编写了 90%的总体置信水平( α )。在 10 次初始运行后计算第一批 ICs(如果运行> = 10: ),即。我们使用 2345 作为随机数序列的种子值。规定每次运行时间长度的预定义事件( SIM_TIME )是 30 天 24 小时/天。***
**#...................................................................confidence = [0.96667, 0.96667, 0.9, 0.96667]
abs_err = [3.00, 2.00, 3.00, 2.00]
l_end = Falsemax_numb_of_runs = 1000
numb_of_runs = max_numb_of_runsseed_value = 2345
prbnumgen = RandomState(seed_value)SIM_TIME = 30 * 24
stop_arrivals = 720 ## for the verification stepglobal df
column_labels = ["Delay in Queue","In Repair",
"Out of System","Machines in Queue"]
df = pd.DataFrame(columns=column_labels)for run in range(numb_of_runs): failures, repaired = [],[]
in_queue, in_service = [],[]
out_system = []
tme_in_queue, len_in_queue = [],[] #Set up the simulation environment
env = simpy.Environment() repair_persons=simpy.Resource(env,
capacity = NUMBER_REPAIR_PERSONS) env.process(machine_failure(env, repair_persons))
env.run(until = SIM_TIME)
calc_measures() if run >= 10:
calc_ICs() if l_end == True:
print_output()
break**
分析
在第 4 篇文章中,我们描述了为一个特定的性能测量建立一个置信区间的固定样本量程序。在第 5 篇文章中,我们描述了一个为特定的性能度量建立置信区间的连续过程。在本文中,我们将前面的想法扩展到同时分析多个性能指标。
表 1 总结了输出数据:在 489 次独立运行后,我们可以以大约 90%的置信度声称,队列中的平均延迟包含在 96.667%置信区间[67.1,73.1]分钟内,平均修复时间包含在 96.667%置信区间[36.26,36.52]分钟内,队列中故障机器的平均数量同时包含在 96.667%置信区间[7.48,8.26]内。我们还计算了机器离开系统的平均时间(90%的置信度)。从概念上讲,这个值必须大约是平均排队延迟加上平均修复时间之和。

表 1,作者用 Matplotlib 做的。
表 2 恢复了一个 95%和两个 97.5% IC 的相同输出数据(0.05+0.025+0.025 = 0.1 =α)和相同的绝对误差。表格之间性能测量的微小差异完全是由于模拟输出的随机性。**

作者用 Matplotlib 做的表 2。
如前所述,良好的概率和统计知识对于每个离散事件模拟研究都是至关重要的。
1: Law,A.M. & Kelton,W.D. (2000)模拟建模与分析。波士顿麦格劳希尔公司。
SimPy 仿真简介
原文:https://towardsdatascience.com/introduction-to-simulation-with-simpy-8d744c82dc80
第三部分:排队系统方案的比较

本文是与使用 SimPy 框架的模拟技术应用相关的系列文章的第三篇。
在的第一篇文章中,提供了关于离散事件模拟的基本概念和原理的概述。我们定义了模型、模拟技术、什么是离散事件模拟(DES) 以及 SimPy 库的特征。我们描述了一些在模拟研究中使用的常见概率分布,以及我们如何生成模拟这种分布的数字序列。
在第二篇文章中,我们描述了排队系统(等待队列系统)的基本原理以及最常用于评估它们的性能指标。最后,我们用 SimPy 模拟了一个单服务台排队系统。
在本文中,我们将描述用于分类排队模型的肯德尔符号。此外,我们将模拟另一个等待系统,强调模拟技术的一个巨大优势:快速、经济地评估所研究系统的各种备选方案的能力。
排队模型的肯德尔符号
英国数学家和统计学家大卫·乔治·肯德尔在 1953 年提出了一种对排队模型进行分类的符号。他用三个字母写成 A/S/c ,其中 A 描述到达过程中的到达间隔时间, S 表示服务时间分布, c 表示服务人数。A 和 S 最常见的可以取的值是 M,D 或G:M隐含一个指数(马尔可夫)分布; D 暗示确定性(非概率性)分布; G 暗示非指数概率分布。当然, c 是整数。
后来又增加了三个字母,这样现在的记法就有了下面的形式: A/S/c/K/N/D ,其中 K 是等候区的容量, N 是潜在顾客的人口数, D 是排队纪律。如果 K 被省略,等待区域被认为是无限的。如果省略 N,则假设调用人口为无穷大。如果省略 D,则假定队列规则为 FIFO (先进先出)。
让我们看一些例子。
M/M/1 :到达间隔时间呈指数分布,服务时间也呈指数分布,一个服务员,无限等待区,无限呼叫人口,先进先出原则。
M/D/2/10/100/LIFO :到达间隔时间服从指数分布,服务时间服从确定性分布,两台相同的服务器,有限的排队长度(只有 10 个顾客可以等待),有限的顾客数量(最多 100 个顾客可以到达系统),排队规则为 LIFO (后进先出)。
G/G/4///优先级:到达间隔时间非指数概率分布,服务时间非指数概率分布,4 个服务器,无限等待区,无限客户群,按照一个优先级条件服务部分客户。
利用 SimPy 做管理决策
我们将使用 SimPy 来模拟工厂中机器的故障及其相应的修复。管理层应该做出关于维修人员数量的决定,这将减少机器停止工作的时间,而不是雇佣额外的维修人员。
一名数据科学家受雇对当前的运行条件进行模拟研究,修理工的数量不断增加,为每一个备选方案指明某些性能指标,以量化其成本。
她清楚地解释说,正在研究的系统是一个等待系统,其中机器是随机发生故障的客户,由服务器 ( 修理工)修理,他们花费随机的时间来修理它们。她发现每台机器平均每 10 分钟就出一次故障,机器故障间隔时间是指数级的。她还发现服务时间(以分钟为单位)并不遵循已知的概率分布,而是由下表表示:

表 1:机器维修时间及其相应的概率。图片作者。
根据肯德尔符号,它是一个 M/G/c 排队系统。
她编写了以下 Python 代码来获得所需的性能指标。
首先,她导入了 Pandas 库作为 pd, Numpy 作为 np ,以及名为 SimPy 的面向对象、基于流程的离散事件模拟框架。她还导入了 scipy.stats 模块,该模块包含了再现服从均匀和指数分布的随机数序列的方法。最后,她导入 matplotlib 作为 plt 和 seaborn 作为 sns 用于制图。
import pandas as pd
import numpy as np
import simpyfrom scipy.stats import uniform
from scipy.stats import exponimport matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animationimport seaborn as sns
修理时间( time_service )随机变量必须是离散的,并按如下方式产生:一个列表( list_of_minutes )表示修理中不同的平均时间;四个变量( prob1,prob2 ,…)表示它们相应的离散概率(表 1)。她对这些变量求和并四舍五入,以获得四个连续子区间([0,0.1];[0.1, 0.4];[0.4, 0.8];[0.8, 1.0]).每个子区间的最终值被传递给一个列表( list_of_probs ),两个列表都被转换为数据帧。然后两个数据帧在 df_service 中连接,另一个数据帧与从均匀分布获得的随机变量一起用于函数修复以确定修复时间值。
包括一个初始化模块,其中她指示用于模拟机器故障间隔时间的指数分布的参数值。她还初始化了模拟结束的时间,以及将用于存储中间结果的变量和列表。她还包括了计算修复时间随机变量的程序。
# Initialization moduleMACHINE_FAILURE_MEAN = 10## Times in repairing
list_of_minutes = [15,30,40,50]# discrete probabilities for times in repairing
prob1, prob2, prob3, prob4 = 0.1, 0.3, 0.4, 0.2prob1 = round(prob1, 4)
prob2 = round(prob1 + prob2,4)
prob3 = round(prob2 + prob3,4)
prob4 = round(prob3 + prob4,4)
list_of_probs = [prob1, prob2, prob3, prob4]df1 = pd.DataFrame(list_of_minutes, columns = ['minutes'])
df2 = pd.DataFrame(list_of_probs, columns = ['range'])
df_service = pd.concat([df1, df2], axis = 1)SIM_TIME = 100max_length, number_rows = 0, 0
avg_length, avg_delay = [],[]
avg_out_system, avg_in_service = [],[]
记住SimPy 为数据分析师提供主动组件:流程生成实体,如故障机器,以及被动组件:资源如维修人员。
分析师编写了一个生成器函数( machine_failure )来模拟机器的随机故障,该函数调用另一个函数( repairing ),其中机器想要使用有限能力的资源(修理工)。 yield env.timeout() 是一种离散事件,在经过一定的模拟时间后触发。方法 request() 生成一个事件,该事件在资源再次变得可用时被触发( yield req )。
def machine_failure(env, number_repair): # counter of failures
failure_number = 0 while True:
## exponential distribution for failures
next_failure = expon.rvs(scale = MACHINE_FAILURE_MEAN ,
size = 1) # Wait for the failure
yield env.timeout(next_failure)
time_of_failure = env.now
failures.append(time_of_failure)
failure_number += 1
print('failure %3d occurs at %.2f' %
(failure_number, env.now)) env.process(repairing(env, number_repair,
failure_number, time_of_failure))#...................................................................def repairing(env, number_repair, failure_number, time_of_failure):
with repair_persons.request() as req:
print('%3d enters the queue at %.2f' %
(failure_number, env.now))
queue_in = env.now
length = len(repair_persons.queue)
tme_in_queue.append(queue_in)
len_in_queue.append(length) yield req
print('%3d leaves the queue at %.2f' %
(failure_number, env.now)) queue_out = env.now
length = len(repair_persons.queue)
tme_in_queue.append(queue_out)
len_in_queue.append(length) # random variate with uniform distribution
r_v = uniform.rvs(size=1)
print(r_v) # setting the repair time
for i,row in df_service.iterrows():
probab = df_service.loc[i, 'range']
if r_v < probab:
time_service = df_service.loc[i, 'minutes']
break yield env.timeout(time_service)%
print('%3d stays at service %.2f' %
(failure_number,time_service)) time_repaired = env.now
repaired.append(time_repaired) time_out_system = time_repaired - time_of_failure
out_system.append(time_out_system) time_in_queue = queue_out - queue_in
in_queue.append(time_in_queue) time_in_service = time_service
in_service.append(time_in_service)
接下来,她编写了用于计算队列中的平均延迟、队列中机器的平均数量、机器离开系统的平均时间以及机器被修复的平均时间的函数。
每个备选方案都包括一个动画,显示等待维修的机器数量的变化。动画是使用 matplotlib 和 T2 seaborn 库开发的。
#...................................................................def avg_line(df_length):
## finds the time weighted average of the queue length # use the next row to figure out how long the queue
was at that length df_length['delta_time'] = df_length['time'].shift(-1) - df_length['time'] # drop the last row because it would have an infinite time span
df_length = df_length[0:-1] avg =np.average(df_length['len'],weights=df_length['delta_time']) return avg#...................................................................def calc_averages(): df3 = pd.DataFrame(tme_in_queue, columns = ['time'])
df4 = pd.DataFrame(len_in_queue, columns = ['len'])
df_length = pd.concat([df3, df4], axis = 1) # calculate the y lim for the number of machines in queue chart
max_length = df4['len'].max(axis = 0) # calculate the number of frames in the animation
number_rows = len(df4) avg_length.insert(altern, avg_line(df_length))
avg_delay.insert(altern, np.mean(in_queue)) avg_out_system.insert(altern, np.mean(out_system))
avg_in_service.insert(altern, np.mean(in_service)) print('Alternative number: %1d' %(altern+1))
print('The average delay in queue is %.2f' %
(avg_delay[altern])) print('The average number of machines in queue is %.2f' %
(avg_length[altern])) print('The average time machines out of system is %.2f' %
(avg_out_system[altern])) print('The average time machines were being repaired is %.2f'
%(avg_in_service[altern])) #.............................................................
Writer = animation.FFMpegWriter(fps=2,
metadata=dict(artist='Me'), bitrate=1800) fig = plt.figure(figsize=(10,6))
ax = fig.gca()
plt.xlim(0, 100)
plt.ylim(0, max_length + 1)
ax.yaxis.get_major_locator().set_params(integer=True)
plt.title(' Number of Machines in Queue for Alternative %i'
%NUMBER_REPAIR_PERSONS,fontsize=20) def animate(i):
data = df_length.iloc[:int(i+1)] #select data range
p = sns.scatterplot(x = data["time"], y = data["len"],
marker = '*', s = 100, color="r")
p.set(xlabel='Time', ylabel='Number of Machines')
p.tick_params(labelsize=17)
plt.setp(p.lines,linewidth=4) ani = matplotlib.animation.FuncAnimation(fig, animate,
frames= number_rows,interval = 50, repeat=True) altern_chart = 'machines_in_queue_alt %i.mp4'
%NUMBER_REPAIR_PERSONS ani.save(your_path + altern_chart, writer = Writer)
plt.show()
模拟算法的中央核心包括一个环境()的实例,因为所有 SimPy 进程都生活在一个环境中。
首先,她选择了一个种子值( 1234 ),因此随机数生成器可以启动序列来生成模拟均匀分布和指数分布的数字流。她决定评估五个备选方案 (Alt1: 1 修理工;Alt2: 2 名修理工;Alt3: 3 名修理工;Alt4: 4 名修理工;Alt 5: 5 修理工)。该代码为每个备选方案计算 4 个性能指标,并绘制相应的动画。
RANDOM_SEEDS = [1234, 5678, 9012, 3456, 7890]
np.random.seed(seed= RANDOM_SEEDS[1])for altern in range(5):
failures, repaired = [],[]
in_queue, in_service = [],[]
out_system = []
tme_in_queue, len_in_queue = [],[] env = simpy.Environment() NUMBER_REPAIR_PERSONS = altern + 1 repair_persons = simpy.Resource(env,
capacity = NUMBER_REPAIR_PERSONS) env.process(machine_failure(env, repair_persons))
env.run(until = SIM_TIME) calc_averages()
最后,一个表格恢复了性能的措施,以量化其相应的成本。
##..................................................................def print_averages(): round_avg_delay = [round(num, 2) for num in avg_delay]
round_avg_length = [round(num, 2) for num in avg_length]
round_avg_out_system = [round(num, 2) for num in avg_out_system]
round_avg_in_service = [round(num, 2) for num in avg_in_service] listoflists = []
listoflists.append(round_avg_delay)
listoflists.append(round_avg_length)
listoflists.append(round_avg_out_system)
listoflists.append(round_avg_in_service) fig, ax = plt.subplots(1,1)
column_labels = [" Alt. 1", " Alt. 2", " Alt. 3",
" Alt. 4", " Alt. 5"]
row_labels = ["Avg. delay in queue",
"Avg. number of machines in queue",
"Avg. time machines out of system",
"Avg. time in repair"] df=pd.DataFrame(listoflists, columns=column_labels)
ax.axis('tight')
ax.axis('off')
ax.table(cellText = df.values,
colLabels=df.columns, rowLabels=row_labels,
rowColours =["skyblue"]*4,
colColours =["cyan"]*5, loc="center")
ax.set_title("Measures of Performance", fontsize=18,
y= 0.8 , pad = 4) plt.savefig(your_path +'perf_measures.png',
bbox_inches='tight', dpi=150) plt.show()
分析
下面的屏幕截图显示了第三个备选项(3 个修理工)的种子值为 1234 的一次模拟运行后的绩效评估值:

图片作者。
下图显示了同一替代方案中等待维修的机器数量的变化:

作者用 Matplotlib 制作的图
最后,表 2 恢复了每个备选方案的性能指标:

表 2:种子值为 1234 的性能测量。图片作者。
看来,管理层应该选择雇佣两名修理工(备选方案 2)作为最佳解决方案。
但是这个模拟研究有两个严重的缺点:
1)选择 100 作为最终模拟时间完全是任意的。然而,模拟运行的长度根据要研究的系统类型和要量化的性能测量而变化。
2)获得的结果很大程度上依赖于模拟运行中使用的随机数序列。如果我们更改种子值并使用 RANDOM_SEEDS 列表的第二个元素(5678),我们将获得以下汇总表:

表 3:种子值为 5678 的性能测量。图片作者。
我们只改变了种子值,一些性能指标的估计值发生了显著的变化。其原因是它们是随机变量的特殊实现,可能具有较大的方差。
我们将在下面的文章中指出解决这些严重缺陷的方法。
SimPy 仿真简介
原文:https://towardsdatascience.com/introduction-to-simulation-with-simpy-8e7187c6eb82
第 4 部分:终止模拟的输出数据分析

本文是与使用 SimPy 的离散事件模拟(DES) 技术应用相关的系列文章的第四篇。
第 1 篇:我们介绍了 DES 的一些基本概念,描述了一些在仿真应用中经常用到的概率分布函数。
第 2 篇:我们描述了表征排队系统(等待线系统)的组件及其最常见的性能度量。
第 3 条:我们描述了用于分类排队模型的 Kendall 符号,并使用 SimPy 来模拟工厂运营中机器的随机故障,以帮助管理层决定减少机器停工时间的修理工数量。
在上一篇文章中,我们指出了用 Simpy 进行的许多模拟研究中的两个常见错误:1)任意选择最终模拟时间;2)进行单次模拟运行,并使用输出作为最终结果。
在本文中,我们将开始分析模拟研究的输出数据,以指出避免犯这些概念性错误的方法。
模拟类型
关于输出数据分析,我们区分两种类型的模拟:1)终止模拟;2)非终止模拟。
一个终止模拟是一个有预定义事件的模拟,该事件规定了每次运行的长度。该终端事件可能是与时间相关的;例如:一家零售商店,营业时间为上午 9 点至下午 5 点。我们希望通过运行模拟研究来评估一些绩效指标,因为终止模拟的最终事件是 8 小时的模拟时间已经过去,商店是空的1。终端事件也可以是与系统被清空或清空的预定义状态相关的;例如:一家制造商收到一份订单,要求生产 500 件特定设计的产品。他决定模拟几种制造配置,以选择一种能保证他以最低成本满足最后期限的配置。在这种情况下,终端事件是已经制造了 500 件1。
在开始模拟运行之前,必须完全定义终端事件,其发生时间可以是随机变量。系统的初始条件也必须事先明确定义,因为它们通常会影响运行结束时计算的性能指标。
另一方面,非终止模拟是一种没有指定运行长度的预定义事件的模拟。从概念上讲,模拟理论上可以无限期地进行下去;在实践中,分析师确定运行模型的合适时间长度。无终止模拟的一个经典例子是一家制造公司,它有 24/7 的活动和相当可预测的需求。我们希望估计一些稳态性能指标,以便更好地理解这种复杂系统的动态特性。
将模拟研究分类为终点或非终点的决定不仅取决于系统的性质,还取决于研究的目标。如果目标是确定被研究系统的短期动态行为,我们将开发一个终止模拟。另一方面,如果目标是确定其稳态行为,我们将开发一个非终止模拟。
在模拟运行的早期阶段,性能指标在稳定之前会有明显的波动。这段时间称为瞬态,终端模拟的输出数据可能包括瞬态的大量值。这就是为什么在终止模拟中非常仔细地选择初始条件是至关重要的。在非终止模拟(非常长的运行)中,瞬态的影响被稀释,并且通常在计算性能度量时被消除。
终止模拟的输出数据分析
模拟是基于计算机的统计抽样实验1。因此,我们需要统计技术来分析模拟实验。
我们借助中心极限定理 ( C.L.T. )来处理样本数据。它指出样本均值的分布将接近正态分布,均值等于总体均值,方差等于 S2/n,其中 S2 是总体方差, n 是数据中的观察次数。
然后我们转到置信区间(CI)。我们需要计算模拟输出数据的置信区间,因为样本均值总会有一些误差。基于中心极限定理,置信区间表示这种误差的大小。

图 1,资料来源:http://mlwiki.org/index.php/Confidence_Intervals_for_Means
要使用前面的公式,我们至少需要 30 次观察。t 分布(图 2)用于较小的样本量。

图 2,来源:http://mlwiki.org/index.php/Confidence_Intervals_for_Means
因此,如果我们想模拟一个被归类为终结系统的系统,我们必须:
a)定义终端事件;
b)确定初始条件;
c)进行 n 独立复制(运行)。每次运行的独立性通过在每次运行中使用不同的随机数流来实现。每次复制必须以相同的初始条件开始;
d)计算每个性能测量的点估计和置信区间。
让我们和 SimPy 一起做。
用 SimPy 进行模拟
一个小作坊专门生产艺术品。到货量呈泊松分布,平均每 10 小时有 1 个工作。该车间有两个工作站,每台机器配备一名操作员,所有工作都需要在这两种机器上进行处理。假设每个工作站的加工时间分别以 7 和 5 为均值呈指数分布。这家商店按照先进先出的原则安排工作。
公司的所有者计划进行某些改进以增加公司的产量。作为第一步,他们决定对当前系统进行模拟研究,获得一些性能度量,然后将它们与提议的改进进行比较。
获取当前系统性能度量的代码如下:
首先,我们导入 Pandas 作为 pd, Numpy 作为 np ,Simpy 和 scipy 用于统计计算和生成具有指数分布的浮动随机数。最后,我们导入 matplotlib 作为 plt 用于制图。
import pandas as pd
import numpy as np
from numpy.random import RandomStateimport simpyfrom scipy import stats
from scipy.stats import exponimport matplotlib
import matplotlib.pyplot as plt
包括一个初始化模块,其中我们指出了工件到达率、表征加工时间的指数分布的参数以及工作站的数量。我们创建了一个数据框架,用于存储中间结果。
JOBS_ARRIVAL_RATE = 1/10WORK_STATION1_MEAN = 7
WORK_STATION2_MEAN = 5NUMBER_WORK_STATION1 = 1
NUMBER_WORK_STATION2 = 1column_labels = ["Delay WK1","Delay WK2",
"Util. WK1","Util. WK2", "Avg Length WK1"]
df = pd.DataFrame(columns=column_labels)
我们编写了两个 生成器函数(generate _ jobs&process _ jobs)来模拟作业的到达和处理:
def generate_jobs(env, arrival_stream, arrival_rate,
inital_delay = 0,
stoptime = simpy.core.Infinity,
prng = RandomState(0)): number_of_job = 0
yield env.timeout(inital_delay) #Yield the initial delay while (env.now <stoptime):
inter_arrival_time = prng.exponential(1.0 / arrival_rate)
los_station1 = prng.exponential(WORK_STATION1_MEAN)
los_station2 = prng.exponential(WORK_STATION2_MEAN) number_of_job += 1
jobpr = process_jobs(env,
'Job number:{}'.format(number_of_job),
number_of_job,los_st1 = los_station1,
los_st2 = los_station2) env.process(jobpr)
yield env.timeout(inter_arrival_time)#.........................................................def process_jobs(env, number_of_job, job_number, los_st1, los_st2): # First Work Station
print("{} is scheduled for workstation 1 at hour {:.4f}".format(number_of_job, env.now)) workstation1_schedule_list.append(job_number)
time_workstation1_schedule_list.append(env.now)
jobwk1_request_time = env.now
jobwk1_request = work_station1.request()
workstation1_length_list.append(len(work_station1.queue))
workstation1_timeth_list.append(env.now) yield jobwk1_request print("{} enters to workstation 1 at hour {:.4f}".format(job_number, env.now)) workstation1_operation_list.append(job_number)
time_workstation1_operation_list.append(env.now)
workstation1_length_list.append(len(work_station1.queue))
workstation1_timeth_list.append(env.now) if (env.now > jobwk1_request_time):
print("{} has to wait {:.4f}hours".format(job_number, env.now - jobwk1_request_time)) yield env.timeout(los_st1)
work_station1.release(jobwk1_request)
workstation1_release_list.append(job_number)
time_workstation1_release_list.append(env.now) # Second Work Station
print("{} is scheduled for workstation 2 at hour {:.4f}".format(job_number, env.now)) workstation2_schedule_list.append(job_number)
time_workstation2_schedule_list.append(env.now)
jobwk2_request_time = env.now
jobwk2_request = work_station2.request() yield jobwk2_request print("{} enters to workstation 2 at hour {:.4f}".format(job_number, env.now)) workstation2_operation_list.append(job_number)
time_workstation2_operation_list.append(env.now) if (env.now > jobwk2_request_time):
print("{} has to wait {:.4f} hours".format(job_number, env.now-jobwk2_request_time)) yield env.timeout(los_st2)
work_station2.release(jobwk2_request)
workstation2_release_list.append(job_number)
time_workstation2_release_list.append(env.now)
接下来,描述用于计算队列中的平均延迟、服务器的利用率和工作站 1 的时间加权长度的函数( calc_measures() )。我们将每次运行的结果附加到函数代码最后一行的数据帧中。
def calc_measures(): # Construct dataframes prior to calculations
df_wk1_schdl['Job Number'] = workstation1_schedule_list
df_wk1_schdl['Job Time Sc1'] = time_workstation1_schedule_list
df_wk2_schdl['Job Number'] = workstation2_schedule_list
df_wk2_schdl['Job Time Sc2'] = time_workstation2_schedule_list
df_wk1_opert['Job Number'] = workstation1_operation_list
df_wk1_opert['Job Time Op1'] = time_workstation1_operation_list
df_wk2_opert['Job Number'] = workstation2_operation_list
df_wk2_opert['Job Time Op2'] = time_workstation2_operation_list
df_wk1_reles['Job Number'] = workstation1_release_list
df_wk1_reles['Job Time Rl1'] = time_workstation1_release_list
df_wk2_reles['Job Number'] = workstation2_release_list
df_wk2_reles['Job Time Rl2'] = time_workstation2_release_list df_merge = pd.merge(df_wk1_schdl, df_wk1_opert,
on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk1_reles,
on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk2_schdl,
on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk2_opert,
on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk2_reles,
on='Job Number', how='left')#.......................................
# Computing measures of performance
# Average Delay in Queues df_merge['Delay Wk1'] = df_merge['Job Time Op1'] - df_merge['Job Time Sc1']
df_merge['Delay Wk2'] = df_merge['Job Time Op2'] - df_merge['Job Time Sc2'] mean_delay_wk1 = df_merge['Delay Wk1'].mean()
mean_delay_wk2 = df_merge['Delay Wk2'].mean()
print(' ')
print('Measures of Performance for Run: %1d' %(run))
print('The average delay in queue for workstation 1 is %.2f hours' % (mean_delay_wk1))
print('The average delay in queue for workstation 2 is %.2f hours' % (mean_delay_wk2))#..........................................
# Utilization of the Servers
for i in range(0, len(df_merge)-1):
workstation1_utilization_list.append(df_merge['Job Time Op1'][i+1] - df_merge['Job Time Rl1'][i])
workstation2_utilization_list.append(df_merge['Job Time Op2'][i+1] - df_merge['Job Time Rl2'][i]) wk1_sum_idle = sum(workstation1_utilization_list)
wk2_sum_idle = sum(workstation2_utilization_list) utilization_wk1 = round((1 - wk1_sum_idle / stop_arrivals) * 100, 2)
utilization_wk2 = round((1 - wk2_sum_idle / stop_arrivals) * 100, 2) print('The utlization of the workstation 1 is %.2f%%' % (utilization_wk1))
print('The utlization of the workstation 2 is %.2f%%' % (utilization_wk2))#...............................................
# Time weighted average of the queue length df_l1 = pd.DataFrame(workstation1_length_list, columns = ['len'])
df_t1 = pd.DataFrame(workstation1_timeth_list, columns = ['time'])
df_qlength1 = pd.concat([df_l1, df_t1], axis = 1) # use the next row to figure out how long the queue
was at that length
df_qlength1['delta_time'] = df_qlength1['time'].shift(-1) - df_qlength1['time'] # drop the last row because it would have an infinite time span
df_qlength1 = df_qlength1[0:-1] len_avg_wk1 = np.average(df_qlength1['len'],
weights = df_qlength1['delta_time']) print('The time weighted length of the workstation 1 is %.2f' % (len_avg_wk1))#.....................................................
# list and dataframe for final output listoflists = []
listoflists.append(round(mean_delay_wk1,2))
listoflists.append(round(mean_delay_wk2,2))
listoflists.append(utilization_wk1)
listoflists.append(utilization_wk2)
listoflists.append(round(len_avg_wk1,2)) df.loc[len(df)] = listoflists
我们构建了两个表:1)表示每个独立运行的性能测量的表;2)显示每个绩效测量的样本均值、样本标准偏差以及置信区间的下限和上限的汇总表:
def print_output():
# measures of performance for 10 independent runs
row_labels = ['Run' + str(i+1) for i in range(10)]
fig, ax = plt.subplots(1,1)
ax.axis('tight')
ax.axis('off') runs_table = ax.table(cellText = df.values,
colLabels = df.columns,
rowLabels = row_labels,
rowColours =["skyblue"]*numb_of_runs,
colColours =["cyan"]*5,
cellLoc ='center', loc ="center") ax.set_title("Measures of Performance",
fontsize=18, y= 0.8 , pad = 4) runs_table.auto_set_font_size(False)
runs_table.set_fontsize(8)
plt.savefig(your_path +'twoWKs_perf_measures.png',
bbox_inches='tight', dpi=150) plt.show()
#.....................................................
## confidence intervals
mean = round(df.mean(),2)
sigma= round(df.std(ddof=1),2)
dof = len(df) -1
confidence = 0.90 ## Level of confidence t_crit = np.abs(stats.t.ppf((1-confidence)/2,dof))
inf, sup = (mean-sigma*t_crit/np.sqrt(len(df)),
mean+sigma*t_crit/np.sqrt(len(df)))
inf = round(inf,2)
sup = round(sup,2) df_output = pd.concat([mean, sigma, inf, sup], axis=1) row_labels = ["Delay WK1","Delay WK2","Util. WK1",
"Util. WK2", "Avg Length WK1"]
col_labels = ["Mean", "Std. Dev.", "Lower bound", "Upper Bound"] fig, ax = plt.subplots(1,1)
ax.axis('tight')
ax.axis('off') output_table = ax.table(cellText = df_output.values,
colLabels = col_labels,
rowLabels = row_labels,
rowColours =["skyblue"]*5,
colColours =["cyan"]*4,
cellLoc ='center', loc ="center") ax.set_title("Output Data", fontsize=18, y= 0.8 , pad = 4) output_table.auto_set_font_size(False)
output_table.set_fontsize(8)
plt.savefig(your_path +'twoWKs_output_perf_measures.png',
bbox_inches='tight', dpi=150)
plt.show()
SimPy 算法的核心是:
numb_of_runs = 10
seed_value = 2345
prbnumgen = RandomState(seed_value)hours_run_sim = 30 * 24
stop_arrivals = 400 ## for the verification stepfor run in range(10): workstation1_schedule_list, workstation2_schedule_list = [],[]
workstation1_operation_list,workstation2_operation_list= [],[]
workstation1_release_list, workstation2_release_list = [],[] time_workstation1_schedule_list, time_workstation2_schedule_list = [],[]
time_workstation1_operation_list,time_workstation2_operation_list = [],[]
time_workstation1_release_list, time_workstation2_release_list = [],[] workstation1_length_list, workstation1_utilization_list = [],[]
workstation1_timeth_list, workstation2_utilization_list = [],[] mean_delay_wk1, mean_delay_wk2 = [],[]
utilization_wk1, utilization_wk2 = [],[]
len_avg_wk1, len_avg_wk2 = [],[] listoflists = [] df_wk1_schdl = pd.DataFrame(columns = ['Job Number', 'Job Time Sc1'])
df_wk2_schdl = pd.DataFrame(columns = ['Job Number', 'Job Time Sc2'])
df_wk1_opert = pd.DataFrame(columns = ['Job Number', 'Job Time Op1'])
df_wk2_opert = pd.DataFrame(columns = ['Job Number', 'Job Time Op2'])
df_wk1_reles = pd.DataFrame(columns = ['Job Number', 'Job Time Rl1'])
df_wk2_reles = pd.DataFrame(columns = ['Job Number', 'Job Time Rl2']) #Set up the simulation environment
env = simpy.Environment() work_station1 = simpy.Resource(env, NUMBER_WORK_STATION1)
work_station2 = simpy.Resource(env, NUMBER_WORK_STATION2) env.process(generate_jobs(env, "Type1", JOBS_ARRIVAL_RATE,0,
stop_arrivals, prbnumgen))
env.run(until = hours_run_sim) calc_measures()print_output()
分析
公司所有者对系统的短期行为感兴趣。商店有一个月可预测需求,所以他们决定用 30*24 = 720 小时的模拟时间作为终端事件。
对于初始条件,模型开始时两个工作站都处于空闲状态,但有一个任务准备开始( inital_delay = 0) 。
该研究涉及指数到达间隔时间和指数工作站操作时间 (inter_arrival_time,los_station1,los_station2 )。
一旦确定了到达和服务的概率分布并建立了初始条件,就必须执行数据验证过程来检查模型编码是否正确。模拟研究中的这一重要步骤称为模型验证。为此,我们执行较短的测试运行(stop _ arrives = 400),使用大量 Print 指令来跟踪系统中的作业流程。通过修改输入参数中的一些值并检查输出是否相应地变化,也可以方便地进行测试。模型验证步骤的目标是在继续生产运行之前检测编程错误。
在模型验证步骤之后,我们必须继续进行模型验证步骤。这个问题将在后续文章中描述。
我们进行了 10 次独立的重复试验。如前所述,每次运行的独立性通过在每次运行中使用不同的随机数流来实现。我们将循环 的外的种子值设置在算法的中央核心,以实现跨运行的统计独立性。
生产运行将值 720 (3024)设置为变量hours _ run _ sim&stop _ arrives。*
表 1 显示了两个工作站的平均排队延迟值、它们忙碌的时间百分比以及工作站 1 前面的平均时间长度。显然,可以看出模拟输出的随机性质以及计算每个性能测量的置信区间的需要。

表 1,由作者用 Matplotlib 制作
表 2 显示了每个性能度量的样本均值、样本标准差以及置信区间的下限和上限。我们可以以大约 90%的置信度宣称,工作站 1 中的延迟包含在区间[8.24,11.9]小时内,工作站 2 中的延迟包含在区间[2.26,5.88]小时内。此外,我们声称(具有大约 90%的置信度)工作站 1 在 60.67%到 69.29%的操作时间内是忙碌的,工作站 2 在 43.85%到 51.55%的操作时间内是忙碌的。最后,工作站 1 前面的队列长度总是围绕值 1 振荡。

表 2,由作者用 Matplotlib 制作
前面描述的计算表 2 所示置信区间的程序称为固定样本量程序。该程序包括预先确定要进行的重复次数。它有一个严重的缺点:我们无法控制置信区间的宽度。业主认为延迟 WK1,Util。WK1 和 Util。WK2 IC 宽度过大。他们更希望规模小一些,这样可以更准确地评估提议的变更的影响。
在下一篇文章中,我们将描述一个程序,以确定获得具有预定义精度水平的置信区间所需的重复次数。我们还将指出基于特定的性能度量来比较两个系统的过程。
1: Law,A.M .和 Kelton,W.D. (2000)模拟建模与分析。波士顿麦格劳希尔公司。
SimPy 仿真简介
原文:https://towardsdatascience.com/introduction-to-simulation-with-simpy-b04c2ddf1900
第一部分:基础知识

什么是模拟?
模拟是一种广泛使用的解决复杂决策问题的技术。概念上的想法是使用计算机来模仿各种类型的真实世界系统的操作。为此,建立并试验被研究系统的数学和/或逻辑模型,直到获得足够的知识来解决先前建立的决策问题。
如果该数学模型相对简单,则使用成熟的数学方法(概率论、线性规划、排队论、预测方法)来获得精确解析解是可行的。然而,现实世界的设施或过程正变得越来越复杂和精密,使得不可能获得这样的分析解决方案。因此,理解此类系统运行的最佳解决方案是使用被称为模拟的技术。
总而言之,模拟是一种技术,它包括建立被研究系统的数学模型,并对其进行数值评估,收集数据以估计解决决策问题所需的信息。
什么是模型?
模型是真实系统的简化但有效的表示,目的是代替它进行研究。
模型有几种分类;我们对以下内容特别感兴趣:
1.-描述性与规定性模型:前者描述关系并提供系统性能的输出数据;后者决定所研究系统的最优策略。
2.-确定性与概率性模型:在前者中,假设所有数据都是确定已知的,它们不包含随机成分;在后者中,至少有一些随机成分由概率分布描述。它们也被称为随机模型。
3.-静态与动态模型:静态模型是在特定时间真实系统的表示;一个动态模型会随着时间而发展。
4.-连续模型与离散模型:在前者中,变量随时间不断变化;在后者中,变量只在分开的时间点改变。
什么是离散事件模拟?
仿真模型是描述性的,但它们可以是确定性的或概率性的,静态的或动态的,连续的或离散的。
离散事件模拟是指对系统随时间演变的建模,其中其变量在不同的时间点发生变化。由于分析中的系统通常具有某些随机成分,当我们提到离散事件模拟模型时,我们谈论的是描述性、动态、离散和随机模型。
缩写 DES 用于指代离散事件模拟方法。事件被定义为可能改变系统状态的瞬时事件。DES 中的一个基本组件是将模拟时间从一个值推进到下一个值的机制。通常,使用所谓的下一事件时间提前量。
与排队、库存、制造和材料处理现实世界系统相关的决策问题是 DES 模拟的应用领域。
什么是 SimPy?
SimPy 是一个基于纯 Python 的面向对象的、基于流程的、、离散事件仿真框架1。
让我们描述一下构成前面定义的术语。
根据维基百科:“面向对象编程 ( OOP )是一种基于对象概念的编程范式,它可以包含数据和代码:字段形式的数据(通常称为属性或属性),以及过程形式的代码(通常称为T37)Python 支持与命令式、过程式编程相结合的 OOP。”[2]
仿真建模中有三种不同的方法:1)事件调度方法;2) 活动扫描进场;3) 流程交互方式。在事件调度方法中,未来事件的时间被明确地编码在仿真模型中,并且根据下一事件时间推进机制来调度。活动扫描方法包括检查模拟模型中存在的所有活动,以便确定在下一个模拟时钟推进时可以开始或完成哪一个活动。最后,流程交互方法描述了单个实体在整个系统中的流动,代码模拟了这些实体在它们的路线中的“体验”。 SimPy 使用流程交互方法。
之前描述了离散事件模拟。
SimPy 为数据分析师提供以下组件,用于开发模拟模型:
- 活动组件:流程生成实体,如客户、消息、计算机或排队系统的病人、材料处理系统的车辆、机器、制造的维修工作以及库存系统的供应和需求流程;
- 被动组件:资源如服务器、柜台、急诊室、维修人员和用于有限容量拥塞点的隧道。另外两种类型的无源组件,电平和存储,将在下面的文章中描述;
- 监控变量用于采集数据进行输出数据分析和绘图。
模拟中的概率与统计
由于现实世界的系统表现出高度的随机性,在模拟研究中应用概率和统计的概念变得至关重要。从这个意义上说,识别表征输入数据的概率分布函数非常重要。在评估模拟模型提供的结果时,统计技术的应用同样重要。
在第一篇文章中,我们将描述一些在模拟研究中常用的概率分布函数。用于输出数据分析的统计技术将在后面的文章中描述。
第一分类对应于连续或离散的概率分布函数。连续 PD 由它们的密度函数【f(x)】定义;离散 PD 由概率质量函数定义。质量和密度函数都取决于一个或多个参数。
参数有三种基本类型:(1)形状;(2)规模;(3)位置。
1.-形状参数定义了分布的基本形式。
2.-标度参数的值扩大或缩小沿 x 轴的分布。
3.-位置参数指定 x 轴上相对于零的相应分布的位置。
不一定,每个概率分布必须具有所有三个参数。有些甚至有一个以上的形状参数值。
让我们描述一下离散事件模拟中最常见的概率分布。
均匀分布:表示一个随机变量的连续分布,其最小值( a )和最大值( b )之间的结果为等概率。它为最小值和最大值之间的所有值分配相等的概率。

https://en .维基百科. org/wiki/Continuous _ uniform _ distribution

https://commons . wikimedia . org/wiki/File:Uniform _ Distribution _ PDF _ SVG . SVG
此分布没有形状参数(常数或平坦)。比例参数是差值( b-a ),因为增加( b-a )会扩大分布,而减少间隔长度会缩小分布。位置参数是 a ,因为它控制沿 x 轴分布的位置。
在模拟研究中,当随机变量的特征不是很清楚时,使用均匀分布。当然,在选择 a 和 b 的值时,还是要尽量严谨。
正态分布:这是一个由钟形曲线表示的连续分布。它是最流行的概率分布,特点是对称,均值等于中值,大部分密度接近均值。
正态分布的概率密度函数为:

https://en.wikipedia.org/wiki/Normal_distribution
其中是均值,σ是方差。

https://Commons . Wikimedia . org/wiki/File:Normal _ Distribution _ pdf . SVGinductive load,公共领域,通过 Wikimedia Commons
该分布有一个位置参数(平均值)和一个比例参数(方差σ)。
在模拟研究中,正态分布用于表示服务器中的处理时间、机器项目规格中的错误,或几个自然现象(几个自然发生变量的变化近似正态分布)。
指数分布。这是一个连续分布,其特点是无记忆属性:当前时间对未来结果没有影响,下一个事件发生前的时间也遵循相同的分布。

https://en.wikipedia.org/wiki/Exponential_distribution
λ是比例参数,分布没有位置或形状参数。分布的均值为 1/λ,方差为(1/λ)**2。

https://Commons . Wikimedia . org/wiki/File:Exponential _ probability _ density . SVGnew ystats,CC BY-SA 4.0https://creativecommons.org/licenses/by-sa/4.0,转自维基共享
它是一个右偏分布,有界于零,只有一个比例参数和一个长的右尾。
在模拟研究中,指数分布被广泛用于表示许多现实世界排队系统中的顾客到达间隔时间。它还用于表示各种设施或行业中的机器、计算机和电子设备的故障间隔时间。习惯上用它来表示排队系统中服务时间的分布,尽管这种假设很少是可信的。
泊松分布。它是一种离散分布,广泛用于以某种预定的度量单位(时间或空间)对事件或发生次数进行建模。它的基本假设是:发生是独立的;他们的数量没有限制;事件以恒定的速率发生。在泊松过程中,无论我们考虑的暴露单位有多小,事件都有发生的概率。
因为它是离散分布,所以由它的概率质量函数定义:

【https://en.wikipedia.org/wiki/Poisson_distribution 号
其中λ >=0,出现次数(形状参数),是平均值,也是方差。

【https://commons.wikimedia.org/w/index.php?curid=3817954】T4By inductive load——自制,Mathematica,Inkscape,公共领域,
在模拟研究中,泊松分布用于模拟排队系统中顾客的到达时间,或者库存系统中每个顾客所需的产品数量。
威布尔分布:这是一种连续分布,根据其参数值的不同,可以采取多种不同的形式。

https://en.wikipedia.org/wiki/Weibull_distribution
其中 k > 0 是形状参数,λ > 0 是比例参数。

https://commons.wikimedia.org/w/index.php?curid=9671814由卡利莫——自己的作品,继菲利普·利奇之后。,CC BY-SA 3.0,
威布尔分布的巨大效用在于,根据其参数取值,它可以近似为指数分布(k = 1),非常接近正态分布(k = 3.25),或瑞利分布(k = 2,λ = √2 σ)。
我们在模拟研究中使用威布尔分布来模拟故障分析、疲劳试验和完成一项确定任务的时间。在这种分布中,发生的概率具有记忆性(随时间变化)。
为模拟研究生成概率结果
为了开发我们的离散事件模拟研究,我们首先需要识别表征输入数据的概率分布函数。然后,我们需要生成模拟这种分布的数字序列。
从概率分布函数生成的结果被称为随机变量。我们说的是随机数。在 DES 中,我们将随机数定义为均匀分布在 0 和 1 之间的数。我们需要一种能够生成这样的数列的算法;幸运的是,每一种技术编程语言都有能够提供这种流的算法。
0 和 1 之间均匀分布的数字序列被称为伪随机数,因为它们是通过确定性过程生成的。库中包含的算法经过多次统计测试,直到序列尽可能接近真正的随机流。
在 Python 中,有两组随机变量生成函数:(1)来自 Python 标准库中的随机模块;(2)来自 scipy.stats 模块的随机变量发生器。两者都是基于Mersenne Twister算法,这是松本诚和西村拓治在 1997 年开发的伪数字生成器。这是一款经过充分测试的生成器,基于梅森素数 219937–1,可在 219937–1 的周期内产生 53 位精度浮点。
在模拟研究中,通常需要再现随机数序列,尤其是在试图比较同一系统对不同备选方案的响应时。随机数生成器需要一个数字来启动序列(a种子值)。默认情况下,它使用当前系统时间。但是,如果需要再现流,每次需要重新开始序列时,都必须编写类似于 random.seed(1234) 的代码。因为 Scipy 也使用 Numpy 随机生成器,所以使用 Numpy 种子函数是可行的: np.random.seed(1234)。
scipy.stats 模块包括 80 多个连续随机变量和至少 10 个离散随机变量。产生随机变量的方法是 rvs 。分布的参数(位置、形状、比例)是标准化的形式。阅读相应的文档很重要,因为如前所述,根据发行版的不同,参数的数量可能会有所不同。
要生成 100 个在 1 和 6 之间均匀分布的浮点随机数:
from scipy.stats import uniformr_uniform = uniform.rvs(loc = 1, scale = 5, size = 100)
要生成 100 个在 1 和 9 之间均匀分布的整数随机数:
from scipy.stats import randintr_unif_di = randint.rvs(1,10, loc = 0, size = 100)
要生成 100 个正态分布的浮动随机数,其中 0 均值和方差等于 3:
from scipy.stats import normr_normal = norm.rvs(loc = 0, scale = 3, size = 100)
要生成 100 个指数分布的浮点随机数,平均值等于 1/标度:
from scipy.stats import exponr_expon = expon.rvs(scale = 3, size = 100)
要生成 100 个具有泊松分布的整数随机数,其形状参数对应于平均值和方差:
from scipy.stats import poissonr_poisson = poisson.rvs(0.6, size = 100)
生成 100 个形状参数为 c > 0 的威布尔分布的浮点随机数(维基百科图表中的 k)。
from scipy.stats import weibull_minr_weibull = weibull_min.rvs(c, size=1000)
总结:这是致力于介绍 SimPy、一个用于模拟研究的 Python 框架系列的第一篇文章。本文的概念是在进入编码阶段之前提供基本概念和原则的概述。
参考
【2】:https://en.wikipedia.org/wiki/Object-oriented_programming
如果你发现了这篇感兴趣的文章,请阅读我之前的(https://medium . com/@ Dar . wtz):
别忘了订阅我的邮箱来接收以下文章。
SimPy 仿真简介
原文:https://towardsdatascience.com/introduction-to-simulation-with-simpy-e27cd7b1ff47
第 5 部分:模拟实验的顺序抽样程序

本文是一系列文章中的第五篇,介绍使用 SimPy 框架的离散事件模拟技术的概念和方法。
在第 4 篇中,我们根据指定每次运行长度的预定义事件的发生来区分终止和非终止模拟。此外,在同一篇文章中,我们开始分析模拟研究的输出数据。
模拟技术通常用于评估物理上不存在的系统的性能,或者当希望知道存在的系统中的变化的影响但不需要在现实中执行它们时。
为此,建立被研究系统的数学和/或逻辑模型并进行实验,直到对系统行为有足够的了解来解决之前建立的业务问题。
这些实验被设计为统计实验。通常总结模拟实验输出数据的方式是用置信区间( CIs ) 。在第 4 篇中,我们开发了用于终止模拟的输出数据分析,使用名为固定样本量程序的程序计算置信区间。这种方法避免了输出数据的一些统计问题,但是分析员不能控制置信区间的宽度。
另一方面,有一个名为顺序采样的程序,它允许分析师获得一个具有预定精度水平的置信区间。
在第五篇文章中,我们将使用与第四篇文章相同的示例来开发顺序过程,比较结果并指出两个过程的优缺点。
绝对精度与相对精度
在许多模拟研究中,常见的操作模式包括对模型进行编码,并进行任意长度的单次模拟运行。从该运行中获得的性能度量被视为所研究模型的相应真实估计。
这一过程是不正确的,因为我们使用从特定概率分布中得出的随机样本(特定的种子值)来浏览我们的模拟模型。我们获得了我们的绩效评估,但是它们可能有很大的差异。
因此,为了减少与单次运行相关的方差,相同的模型被运行多次,并且连续的结果被平均以获得点估计量,【Xk】。然后,根据图 1 所示的公式构建该点周围的置信区间。

图 1:置信区间。
其中 k 为运行次数, tη,k1为自由度为k1的 t 分布的 (1 + η)/2 分位数, S ₖ 为样本方差。
性能指标估计的精度习惯上是指置信区间的半宽度。它由图 2 所示的等式描述:

图 2:置信区间的半宽度。
Xk 的精度可能是绝对或相对。
如果对 X 的估计是这样的│Xk——│=图, Xk 有一个绝对误差图(绝对精度)。
如果对 X 的估计是这样的:Xk│Xk——│/││=γ, Xk 有一个γ的相对误差(相对精度)。
其中为总体均值【1】。
在第 4 篇中,我们指出固定样本程序有一个严重的缺点,因为分析员无法控制 IC 的宽度;所以他无法控制估算的精度。因此,如果模拟运行产生的置信区间的半宽度必须强制满足分析师的精度要求,我们必须使用顺序程序。
顺序程序
该程序假设 X1,X2,…..Xk 是来自模拟的一系列 k 独立且同分布( IID )的样本(性能测量)。该程序的目的是获得T5 的估计值和置信水平为 100(1 — η )的置信区间,绝对精度为。**
绝对精度的程序如下:
1.为置信度选择一个值 η (习惯上为 90%或 95%)。
2.选择一个值和作为特定性能测量和特定研究的精度要求。
3.进行 k₀模拟的初始复制,并设置 k = k₀.
4.根据图 2 所示的公式计算点估计值和置信区间的半宽度。
5.如果 HWn,k > ,用 k+1 替换 k,再运行一次模拟,然后返回步骤 4。
6.当 HWn,k <=时,停止,使用 Xk 作为点估计量,根据图 1 计算最终置信区间。
需要注意的是,对于相对精度和实际相对误差 γ ,我们必须在步骤 5 和 6 中计算“调整后的”相对误差1:
HWn,k < = γ' ,其中 γ'= γ/(1+ γ) 1。
用 SimPy 进行模拟
在第 4 篇中,我们用 SimPy 模拟了一个专门生产艺术品的工作车间。工件到达车间的速度服从泊松分布,平均速度为每 10 小时 1 个工件。该车间有两个工作站,每台机器一名操作员,所有工作都需要在这两种类型的机器上进行处理。假设每个工作站的加工时间分别为 7 和 5 的指数分布。这家商店按照先进先出(FIFO)的原则安排工作。
在第 4 篇文章中,我们使用固定样本程序计算了一些性能指标的置信区间。在本文中,我们将使用上述顺序程序计算一个具有绝对精度的特定性能指标。
代码如下:
首先我们导入了以下库:Pandas、Numpy、SimPy、SciPy 和 Matplotlib。
*import pandas as pd
import numpy as np
from numpy.random import RandomStateimport simpy
from scipy import stats
import matplotlib.pyplot as plt*
包括一个初始化模块来指示任务到达率、表征处理时间的指数分布的参数以及工作站的数量。数据帧 df 将用于存储中间结果。
*# initialization module
# Unit of time = hoursJOBS_ARRIVAL_RATE = 1/10WORK_STATION1_MEAN = 7
WORK_STATION2_MEAN = 5NUMBER_WORK_STATION1 = 1
NUMBER_WORK_STATION2 = 1column_labels = ["Delay WK1","Delay WK2","Util. WK1",
"Util. WK2", "Avg Length WK1"]df = pd.DataFrame(columns=column_labels)*
作业的到达和处理用两个生成器函数 : 1) generate_jobs 建模; 2) 流程 _ 岗位。我们在打印语句中放了一些##字符,因为我们只将它们用于验证目的。
*def generate_jobs(env, arrival_stream, arrival_rate,
inital_delay = 0,
stoptime = simpy.core.Infinity,
prng = RandomState(0)): number_of_job = 0
yield env.timeout(inital_delay) #Yield the initial delay while (env.now <stoptime):
inter_arrival_time = prng.exponential(1.0 / arrival_rate)
los_station1 = prng.exponential(WORK_STATION1_MEAN)
los_station2 = prng.exponential(WORK_STATION2_MEAN) number_of_job += 1
jobpr = process_jobs(env,
'Job number: {}'.format(number_of_job), number_of_job,
los_st1 = los_station1, los_st2 = los_station2) env.process(jobpr)
yield env.timeout(inter_arrival_time)#.................................................................
def process_jobs(env, number_of_job, job_number, los_st1, los_st2): # First Workstation
##print("{} is scheduled for workstation 1
at hour {:.4f}".format(number_of_job, env.now)) workstation1_schedule_list.append(job_number)
time_workstation1_schedule_list.append(env.now) jobwk1_request = work_station1.request()
workstation1_length_list.append(len(work_station1.queue))
workstation1_timeth_list.append(env.now) yield jobwk1_request
##print("{} enters to workstation 1
at hour {:.4f}".format(job_number, env.now)) workstation1_operation_list.append(job_number)
time_workstation1_operation_list.append(env.now)
workstation1_length_list.append(len(work_station1.queue))
workstation1_timeth_list.append(env.now) ##if (env.now > jobwk1_request_time):
##print("{} has to wait {:.4f} hours".format(job_number,
env.now - jobwk1_request_time)) yield env.timeout(los_st1)
work_station1.release(jobwk1_request)
workstation1_release_list.append(job_number)
time_workstation1_release_list.append(env.now)# Second Workstation
##print("{} is scheduled for workstation 2 at hour
{:.4f}".format(job_number, env.now)) workstation2_schedule_list.append(job_number)
time_workstation2_schedule_list.append(env.now) jobwk2_request = work_station2.request()
yield jobwk2_request ##print("{} enters to workstation 2 at hour
{:.4f}".format(job_number, env.now)) workstation2_operation_list.append(job_number)
time_workstation2_operation_list.append(env.now) ##if (env.now > jobwk2_request_time):
##print("{} has to wait {:.4f} hours".format(job_number,
env.now-jobwk2_request_time)) yield env.timeout(los_st2)
work_station2.release(jobwk2_request)
workstation2_release_list.append(job_number)
time_workstation2_release_list.append(env.now)*
函数 calc_measures() 允许我们计算性能的模型度量:
*def calc_measures(): # Construct dataframes prior to calculations df_wk1_schdl['Job Number'] = workstation1_schedule_list
df_wk1_schdl['Job Time Sc1'] = time_workstation1_schedule_list
df_wk2_schdl['Job Number'] = workstation2_schedule_list
df_wk2_schdl['Job Time Sc2'] = time_workstation2_schedule_list df_wk1_opert['Job Number'] = workstation1_operation_list
df_wk1_opert['Job Time Op1'] = time_workstation1_operation_list
df_wk2_opert['Job Number'] = workstation2_operation_list
df_wk2_opert['Job Time Op2'] = time_workstation2_operation_list df_wk1_reles['Job Number'] = workstation1_release_list
df_wk1_reles['Job Time Rl1'] = time_workstation1_release_list
df_wk2_reles['Job Number'] = workstation2_release_list
df_wk2_reles['Job Time Rl2'] = time_workstation2_release_list df_merge = pd.merge(df_wk1_schdl, df_wk1_opert, on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk1_reles, on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk2_schdl, on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk2_opert, on='Job Number', how='left')
df_merge = pd.merge(df_merge, df_wk2_reles, on='Job Number', how='left')#.......................................
# Computing measures of performance
# Average Delay in Queues df_merge['Delay Wk1'] = df_merge['Job Time Op1'] - df_merge['Job Time Sc1']
df_merge['Delay Wk2'] = df_merge['Job Time Op2'] - df_merge['Job Time Sc2']
mean_delay_wk1 = df_merge['Delay Wk1'].mean()
mean_delay_wk2 = df_merge['Delay Wk2'].mean() print(' ')
print('Measures of Performance for Run: %1d' %(run+1) )
print(' ')
print('The average delay in queue for workstation 1 is %.2f hours' % (mean_delay_wk1)) print('The average delay in queue for workstation 2 is %.2f hours' % (mean_delay_wk2))#............................................
# Utilization of the Servers
for i in range(0, len(df_merge)-1): workstation1_utilization_list.append(df_merge['Job Time Op1'][i+1] - df_merge['Job Time Rl1'][i])
workstation2_utilization_list.append(df_merge['Job Time Op2'][i+1] - df_merge['Job Time Rl2'][i]) wk2_sum_idle = np.nansum(workstation2_utilization_list)
utilization_wk1 = round((1 - wk1_sum_idle / stop_arrivals) * 100, 2)
utilization_wk2 = round((1 - wk2_sum_idle / stop_arrivals) * 100, 2) print(' ')
print('The utilization of the workstation 1 is %.2f%%' % (utilization_wk1))
print('The utilization of the workstation 2 is %.2f%%' % (utilization_wk2))
#...............................................
# Time weighted average of the queue length df_l1 = pd.DataFrame(workstation1_length_list,
columns = ['len'])
df_t1 = pd.DataFrame(workstation1_timeth_list,
columns = ['time'])
df_qlength1 = pd.concat([df_l1, df_t1], axis = 1)# use the next row to figure out how long the queue was at that length
df_qlength1['delta_time'] = df_qlength1['time'].shift(-1) - df_qlength1['time']# drop the last row because it would have an infinite time span
df_qlength1 = df_qlength1[0:-1]
len_avg_wk1 = np.average(df_qlength1['len'],
weights = df_qlength1['delta_time']) print(' ')
print('The time weighted length of the workstation 1 is %.2f' % (len_avg_wk1))
#.....................................................
# list and dataframe for final output listoflists = []
listoflists.append(round(mean_delay_wk1,2))
listoflists.append(round(mean_delay_wk2,2))
listoflists.append(utilization_wk1)
listoflists.append(utilization_wk2)
listoflists.append(round(len_avg_wk1,2)) df.loc[len(df)] = listoflists*
接下来,描述用于计算置信区间的函数( calc_ICs() )。当所选性能测量的置信区间的半宽度小于先前定义的绝对精度时,我们将布尔变量( l_end )设置为 True 。
*def calc_ICs(): ## confidence intervals
## define 3 global variables
global df_output, hwic, l_end mean = round(df.mean(),2)
sigma= round(df.std(ddof=1),2)
dof = len(df) -1 t_crit = np.abs(stats.t.ppf((1-confidence)/2,dof))
inf, sup = (mean-sigma*t_crit/np.sqrt(len(df)),
mean+sigma*t_crit/np.sqrt(len(df))) inf = round(inf,2)
sup = round(sup,2)
hwic= (sup-inf)/2 if hwic[0] <= abs_err_delay_wk1:
l_end = True print('')
print(round(hwic[0],2),abs_err_delay_wk1, l_end )
df_output = pd.concat([mean, sigma, inf, sup], axis=1)
print(df_output)*
我们编写了函数 print_output() 来显示最近 20 次独立运行的性能度量,并打印输出数据表,显示每个性能度量的样本均值、样本标准差以及 CIs 的下限和上限。
*def print_output(): # Table showing measures of performance
for the last 20 independent runs df_last_20 = df.tail(20)
row_labels = ['Run' + str(i+1) for i in range(run+1-20, run+1)] fig, ax = plt.subplots(1,1)
ax.axis('tight')
ax.axis('off') runs_table = ax.table(cellText = df_last_20.values,
colLabels = df.columns, rowLabels = row_labels,
rowColours =["skyblue"]*(20),
colColours =["cyan"]*5,
cellLoc='center', loc="center") ax.set_title("Measures of Performance",
fontsize=18, y= 1.2 , pad = 4) runs_table.auto_set_font_size(False)
runs_table.set_fontsize(8)
plt.savefig(your_path +'twoWKs_perf_measures.png',
bbox_inches='tight', dpi=150)
plt.show()
#..................................................... ## Output Data Table
col_labels = ["Mean", "Std. Dev.", "Lower bound", "Upper Bound"] row_labels = ["Delay WK1 (h)","Delay WK2 (h)",
"Util. WK1 (%)","Util. WK2 (%)","Avg Length WK1"] fig, ax = plt.subplots(1,1)
ax.axis('tight')
ax.axis('off') output_table = ax.table(cellText = df_output.values,
colLabels = col_labels, rowLabels = row_labels,
rowColours =["skyblue"]*5,
colColours =["cyan"]*4,
cellLoc='center', loc="center") ax.set_title("Output data for %i independent runs" %(run+1),
fontsize=18,y= 0.8, pad = 4) output_table.auto_set_font_size(False)
output_table.set_fontsize(8)
plt.savefig(your_path +'twoWKs_output_perf_measures.png',
bbox_inches='tight', dpi=150)
plt.show()*
置信度为 90%、绝对精度为 1.0、k= 10(if run>= 10:)的仿真算法的核心是:
**confidence = 0.90
abs_err_delay_wk1 = 1.00
l_end = Falseinit_numb_of_runs = 500
numb_of_runs = init_numb_of_runsseed_value = 2345
prbnumgen = RandomState(seed_value)
hours_run_sim = 30 * 24
stop_arrivals = 720 ## for the verification stepfor run in range(numb_of_runs): workstation1_schedule_list, workstation2_schedule_list = [],[]
workstation1_operation_list,workstation2_operation_list= [],[]
workstation1_release_list, workstation2_release_list = [],[] time_workstation1_schedule_list, time_workstation2_schedule_list = [],[]
time_workstation1_operation_list,time_workstation2_operation_list = [],[]
time_workstation1_release_list, time_workstation2_release_list = [],[] workstation1_length_list, workstation1_utilization_list = [],[]
workstation1_timeth_list, workstation2_utilization_list = [],[] mean_delay_wk1, mean_delay_wk2 = [],[]
utilization_wk1, utilization_wk2 = [],[]
len_avg_wk1, len_avg_wk2 = [],[]
listoflists = [] df_wk1_schdl = pd.DataFrame(columns = ['Job Number', 'Job Time Sc1'])
df_wk2_schdl = pd.DataFrame(columns = ['Job Number', 'Job Time Sc2'])
df_wk1_opert = pd.DataFrame(columns = ['Job Number', 'Job Time Op1'])
df_wk2_opert = pd.DataFrame(columns = ['Job Number', 'Job Time Op2'])
df_wk1_reles = pd.DataFrame(columns = ['Job Number', 'Job Time Rl1'])
df_wk2_reles = pd.DataFrame(columns = ['Job Number', 'Job Time Rl2']) # Set up the simulation environment
env = simpy.Environment() work_station1 = simpy.Resource(env, NUMBER_WORK_STATION1)
work_station2 = simpy.Resource(env, NUMBER_WORK_STATION2) env.process(generate_jobs(env, "Type1", JOBS_ARRIVAL_RATE, 0,
stop_arrivals, prbnumgen)) env.run(until = hours_run_sim)
calc_measures() if run >= 10:
calc_ICs() if l_end == True:
print_output()
break**
分析
在第 4 篇文章中,我们使用固定样本量的方法计算了一些性能指标的置信区间。我们在每次运行中使用不同的随机数流进行了 10 次独立复制。表 1 显示了工作站 1 中的延迟、工作站 2 中的延迟、工作站 1 & 2 忙碌的时间百分比以及队列 1 的时间加权平均长度的平均值、标准偏差以及 CIs 的下限和上限。分析师认为一些置信区间太大,对商业目的没有意义。

表 1,作者用 Matplotlib 做的。
由于分析师无法控制 CIs 的精度,他决定使用前面代码中指示的顺序程序。他为工作站 1 的延迟设定了 90%的置信度和 1.0 的绝对精度。他选择 3024 = 720 小时的模拟时间作为终止模拟的终止事件,两个工作站都空闲作为初始条件。for 循环**外的种子值23452345**(for run in range(number _ of _ runs):)用于完成跨运行的统计独立性。***
表 2 显示了研究中最后 20 次独立运行的性能测量值。我们需要 208 次运行来获得期望的绝对精度。表 2 中显示的估计值的较大方差清楚地表明模拟输出数据的随机性质以及计算每个性能测量的 ci 的需要。

表 2,作者用 Matplotlib 做的。
表 3 总结了模拟研究的输出数据。它显示了 208 次独立运行后我们的性能测量的平均值、标准偏差和置信区间的下限和上限。我们以大约 90%的把握宣称,1 号工作站的延迟包含在区间【12.53,14.53】小时内。工作站 1 中延迟的置信区间宽度等于 2,因此 IC 的半宽度满足分析师的精度要求。对于该性能测量,从固定样本程序获得的值可以观察到非常重要的差异。

表 3,作者用 Matplotlib 做的。
置信区间被定义为包含具有指定概率的真值的区域。我们必须为我们想要构建的每个配置项定义置信度。还有一个与 CIs 相关的重要量:覆盖概率。置信水平是程序的 名义覆盖概率 。覆盖概率是区间包含真均值的实际概率。如果在推导置信区间中使用的所有假设都满足,则标称覆盖概率将等于覆盖概率。但是,如果不满足某些假设,实际覆盖概率可能小于或大于名义覆盖概率[2]。
尽管顺序程序显著减小了 IC 的半宽度,但在覆盖方面并不总是表现良好。覆盖范围可能会有所不同,因为在模拟实验期间不满足 CI 的假设,或者输出数据的分布与程序假设的分布不同。此外,覆盖率对选择非常敏感。在任何情况下,我们必须意识到使用顺序程序可能会导致严重的承保范围损失。****
在下面的文章中,我们将继续分析模拟研究中存在的这些和其他重要的统计问题。
1: Law,A.M. & Kelton,W.D. (2000)模拟建模与分析。波士顿麦格劳希尔公司。
[2]:https://en.wikipedia.org/wiki/Coverage_probability
使用 ROUGE 评分的文本摘要介绍
原文:https://towardsdatascience.com/introduction-to-text-summarization-with-rouge-scores-84140c64b471
自然语言处理中文本摘要的信息指南

图片由 Unsplash 的 Joshua Hoehne 拍摄
本教程简要介绍了自动文本摘要这一有监督的自然语言处理任务,并详细介绍了量化摘要性能的标准。
在信息爆炸的时代,专业人士在工作或日常生活中可能会消耗大量的文本,通常是报告、文章或科学论文。通常,这些文本包含围绕一些中心主题或思想的细节或阐述。然而,在这些过多的文本文档中,有时我们要么只想捕捉某些文档的要点,要么搜索与主要思想相关的内容。一个类似的类比是浏览报纸的标题,尽管一篇文章的标题,作为一句俏皮话,并不是最好的总结形式。
这也是机器学习和人工智能可以介入的地方,因为自动文本摘要是自然语言处理(NLP)任务中的一个重要领域。
1.两种类型的文本摘要
自动文本摘要训练通常是受监督的学习过程,其中每个文本段落的目标是相应的金色注释摘要(人类专家指导的摘要)。另一方面,模型生成的摘要或训练后的预测有两种类型:
1.1 抽象概括
摘要是文本段落的主旨,是完全转述的,有时会用原文中没有的词和内容。因此,抽象摘要是一项困难的任务,可能会遇到生成的摘要在语法或逻辑上不一致的情况。通常,它需要最先进的变压器架构来实现稳定的结果。
1.2 摘要概述
摘要,顾名思义,包含从原文段落中提取的单词和短语。虽然有不同的提取技术,但最常见和最简单的一种是按照正确的顺序从文本段落中提取句子。一般来说,前几个句子对文章的意义起着更重要的作用,并且经常被提取出来,尽管文章后面的句子也可以被提取出来。
值得注意的是,在研究社区中,金色注释摘要通常在本质上是抽象的,因为文本段落经常被人类解释成摘要。因此,用于抽象概括的监督学习过程是直接的,因为目标是明确定义的。但是对于逐句提取摘要,有时需要一个额外的步骤。
基于金色注释摘要及其句子数量,从原始文本段落中提取具有最高相似度的相应句子子集。相似度由一个叫做胭脂分数的度量来衡量,我们将在后面讨论。这些提取的句子集,基于金色注释的抽象摘要,在研究界被称为甲骨文摘要。然后,用于提取摘要的监督学习过程基于甲骨文摘要的标记句子。最后,任何提取模型的性能都是生成的提取概要的 ROUGE 分数和 oracle 概要的 ROUGE 分数之间的相对比较。
P.S .由于排列的数量,获得句子的全局最优集合(具有最大 ROUGE 得分)的过程有时在计算上是昂贵的,并且通常用某些算法来近似,例如在 2016 年论文summary runner:用于文档的提取摘要的基于递归神经网络的序列模型 中介绍的贪婪方法。
2.总结模型—进一步阅读
总结模型架构通常更加复杂和费解,因此我们不会在这个简单的教程中讨论它们。但是,在下面的段落中,我们将介绍一些研究论文,以及它们相应的代码库,以供进一步阅读和修改。
在变形金刚时代之前,正如 2017 年的论文所介绍的那样,无论有无注意机制,由 LSTMs 管理的 NLP 模型都没有那么强大。因此,提取摘要模型更有效。一个这样的模型如下:
纽阿尔斯姆
- 描述:关注基于 LSTM 编解码模型的摘要提取模型
- 2016 论文 : 通过提取句子和单词的神经摘要
- 代码库:https://github.com/cheng6076/NeuralSum
在变形金刚时代之后,尤其是随着 BERT 架构的出现(2018 年论文, BERT:深度双向变形金刚语言理解的预训练)。这样,抽象摘要和提取摘要的性能都变得健壮。一些模型如下:
伯特森(伯特文本和伯特布斯)
- 描述:用于抽象和提取总结的类似 BERT 的架构。
- 2019 年论文:带预训练编码器的文本摘要
- 代码库:https://github.com/nlpyang/PreSumm
飞马座
- 描述:基于变压器的编码器-解码器模型,具有用于抽象概括的自我监督目标
- 2019 论文: PEGASUS:用提取的 Gap-sentences 进行抽象概括的预训练
- 代码库:https://github.com/google-research/pegasus
3.胭脂分数简介
接下来,我们继续评估指标,比较模型生成的摘要与黄金注释摘要的接近程度。值得注意的是,在研究社区中,ROUGE score 已经发展成为摘要任务的标准评估标准。ROUGE 代表面向回忆的 Gisting 评价替角,在论文 ROUGE:一个自动评价概要的包中有介绍。虽然 ROUGE 顾名思义是“面向召回的”,但本质上,它同时考虑了候选(模型生成的或预测的)和参考(金色注释的或目标)摘要之间的召回率和精确度。此外,胭脂分数分为胭脂-1、胭脂-2 和胭脂-1 分数。例如,让我们考虑下面这段话:
John loves data science. He studies data science concepts day and night, forgetting to eat at times.
参考摘要:
John really loves data science.
和候选摘要(考虑提取的情况):
John loves data science.
胭脂-1
ROUGE-1 Precision 和 Recall 比较参考摘要和候选摘要之间单字的相似性。对于单字,我们简单的意思是每个比较的记号都是一个单词。例如,我们可以将“John 热爱数据科学”的单词表达为一个 Python 标记列表:
['John','loves','data','science']
通过回忆,我们简单的参考候选摘要所抓取的参考摘要中的单词比例。
通过精度,我们指的是由候选摘要建议的、实际出现在参考摘要中的单词的比例。
例如:
Reference Tokens: ['John','*really*','loves','data','science'](**n_r=5**)
Candidate Tokens: ['John','loves','data','science'] (**n_can=4**)
**Captured Tokens**: ['John','loves','data','science'] (**n_cap=4**)**Rouge-1 Recall = n_cap/n_r = 4/5
Rouge-1 Precision = n_cap/n_can = 4/4**
同样地,我们可以看到,对于由候选摘要准确预测单词的数量,召回奖励,而对于候选摘要中出现的不必要的冗长单词,精确惩罚。精确度差的一个例子是下面的候选摘要:
John really really really loves data science. (**n_can=**7)
在这种情况下,所有的引用标记都被捕获,因此它应该有一个完美的召回分数。但是,由于过于冗长,它的 Rouge-1 精度只有 5/7。
胭脂-2
ROUGE-2 Precision 和 Recall 比较参考和候选摘要之间的二元模型的相似性。对于二元语法,我们的意思是每个比较标记是来自参考和候选摘要的两个连续单词。例如,“John 热爱数据科学”的双字母组合可以表示为以下符号:
['John loves','loves data','data science']
因此,可以计算召回率和精确度:
Reference Tokens: ['John really','really loves','loves data','data science'](**n_r=**4)Candidate Tokens: ['John loves','loves data','data science'] (**n_can=**3)**Captured Tokens**: ['loves data','data science'] (**n_cap=**2)**Rouge-2 Recall = n_cap/n_r = 2/4
Rouge-2 Precision = n_cap/n_can = 2/3**
胭脂-L
ROUGE-L 精度和召回测量参考和候选摘要之间的最长公共子序列(LCS) 单词。对于 LCS,我们指的是在序列中的,但是不一定是连续的。为了理解这一点,让我们看一个带有参考摘要的复杂示例:
John really loves data science very much and studies it a lot.
以及候选人总结(斜体字代表 LCS):
*John* very much *loves data science and* enjoys *it a lot*.
参考标记、候选标记和捕获标记以及相应的精度和召回看起来如下:
Reference Tokens: ['John','really','loves','data','science','very','much','and','studies','it','a','lot'](**n_r=**12)Candidate Tokens: ['John','very','much','loves','data','science','and','enjoys','it','a','lot'] (**n_can=**11)**Captured Tokens**: ['John','loves','data','science','and','it','a','lot'] (**n_cap=**8)**Rouge-2 Recall = n_cap/n_r = 8/12
Rouge-2 Precision = n_cap/n_can = 8/11**
ROUGE-1、ROUGE-2 和 ROUGE-L Precision/Recall 一起很好地表示了模型生成的摘要如何很好地表示黄金注释的摘要。为了使分数更简洁,通常 F1 分数,即精确度和召回率之间的调和平均值,是为所有 ROUGE 分数计算的。

F1 方程式。图片作者。
此外,可以使用 Python 包轻松计算这些 ROUGE 分数。在终端或命令提示符下:
pip install rouge
4.最后的想法
感谢阅读!我希望这篇文章对自动文本摘要的工作原理有所帮助。摘要是自然语言处理中一个令人兴奋的研究领域,有许多潜在的下游应用,如自动标题生成、报告摘要等,也可能产生商业价值。为了进行实验,可以在这里看到一个有趣的总结一段文字的 web 应用程序:
我会写一些关于 NLP 的其他话题。在接下来的相关文章中,我将讨论其他的自然语言处理任务,比如共指消解(即将文章中相互共指的词实体进行聚类)。一定要保持警惕!
干杯!_/_
我还介绍了 NLP 中的一些其他主题。如果你有兴趣,可以看看我下面的一些文章:
https://medium.com/mlearning-ai/introduction-to-hidden-markov-model-hmm-with-simple-ner-d1353ff35842
附:如果你对 加速你的数据科学学习 感兴趣,这里还有一篇关于养成良好学习习惯的极其有用的文章:
感谢阅读!如果您喜欢这些内容,请在 中的 上阅读我的其他文章,并在LinkedIn上关注我。
支持我! —如果你没有订阅 Medium,并且喜欢我的内容,请考虑通过我的推荐链接加入 Medium 来支持我。
https://tanpengshi.medium.com/membership
面向数据科学家的物联网简介:示例和挑战
【2022 年最大趋势
了解它是什么以及与传统数据科学的区别

照片由rom son preech wit在 Unsplash 拍摄
物联网(IoT)终于起飞了。
2022 年,全球将有 144 亿台联网物联网设备。与前一年相比,增长了 18%。
2022 年物联网市场规模为4783.6 亿美元,而企业物联网市场份额约为的三分之一。
预计 2025 年将产生 80 zettabytes 的物联网数据。
有人需要分析这些数据。
什么是物联网?
1999 年,麻省理工学院(MIT)在射频 IDs 的背景下首次使用了“物联网”一词。物联网的定义标准是物理和数字体现、,即通过使用和连接信息来增强“事物”的能力。
物联网没有考虑人类同步生成和消费数据。此外,纯网络环境中的传感器不被视为物联网系统。
从技术上讲, 物联网是通过互联网接入的 物理对象的网络。这些对象包含与内部状态或外部环境交互的嵌入式技术。换句话说,当事物能够感知和交流时,它会改变决策的方式和地点以及决策人。”
这意味着物联网解决方案由四个组件组成:传感器、网络(通信)、分析(云)和应用。

图片改编自 Isabelle Flückiger 和 Matteo Carbone 的报告《从风险转移到风险防范——物联网如何重塑保险业的商业模式》
这四个组成部分允许利用信息做出比传统的不相连的物理世界更智能的行动。这种“互联事物”的增强是通过三种不同的应用程序实现的:
监控,也就是说,它为做出决策和管理物理世界中的情况提供洞察力
- 通过智能手机传感器评估身体活动
- 在智能手机上显示智能门铃中的视频
- 发动机上的传感器发出的需要维护的早期警告
- 超市冰箱温度的记录
远程控制,即 it需要一些执行器(电子、机械、液压、气动或声音执行器),这些执行器在物理世界中实现由人作出的远程决策
- 苹果“查找我的”应用程序中的“播放声音”
- 在高风险情况下,连接洗衣机和滚筒式干衣机的安全远程禁用功能
- 建筑物暖通空调或安全系统的远程控制
自动化,即不需要人工干预,由简单或更高级的算法做出决定
- 智能关闭阀
- 制造工厂中的装配机器人
- 基于摄像头的访问管理
- 仓库中的自主库存管理机器人
有哪些应用实例?
物联网应用覆盖全球经济的所有领域,物联网在企业和消费者业务中的使用正在走向成熟。
因此,让我们看几个例子,展示物联网驱动的见解的广泛应用。
智能家居 你可能听说过一个智能恒温器,用于监控远程温度和供暖,或者一个智能运动传感器,可以检测盗窃行为,并区分主要居民、他们的宠物和窃贼。
但是应用程序可以变得更加复杂。
智能门环结合运动传感器。基于综合数据的洞察,系统对居民的安全性做出结论。例如,假设门打开了特定的一段时间,并且组合了运动或不运动数据。在这种情况下,如果住户可能没有关门就离开了公寓,应用程序可以得出结论。当居民是最终可能患有痴呆症的老年人时,会向该人及其联系人或应急小组发送警报。
这类应用需要实时、高度准确的结果,且假阳性率低。
医疗保健 可穿戴设备可以带来个性化的健康建议,从身体运动、睡眠和基于识别模式的营养建议开始。
工业环境中的可穿戴设备包含衣服、手套或鞋子中的传感器,并通过警报和预防创建更安全的工作场所。这些传感器每秒钟收集几个生理和环境数据。
数据洞察通过两种方式提供给员工。
首先,它们直接传递给处于危险情况下的员工,需要通过升级触觉、听觉和视觉警报进行立即干预,以实时防止危险行为。
其次,它确定了领先的指标风险因素,如弯曲、扭曲和倾斜运动数据。在新的轮班之前,工人得到他们个人的健康风险行为模式,以及在伤害发生之前如何纠正风险行为的建议。雇主直接获得财务数据,例如,当工人正确提升时节省的成本,即使他们需要更多的时间,但防止了公司的后续成本。这种系统平均减少了 45%的伤害。
此类系统处理实时、动态数据,必须在数小时内提供实时警报和建议的行动。此外,需要解决数据隐私问题。
车辆 汽车中先进的远程信息处理系统包括一个边缘带人工智能的摄像头、全球定位系统和一个加速度计来检测冲击,即它可以检测各种强度的冲击。
这些设备提供两种类型的辅助。首先,它集成了实时风险缓解警报,并通过后视镜附近的语音和显示器警告驾驶员危险情况。这种系统的复杂性很高,因为它需要平衡防止危险情况和分散驾驶员的注意力。因此,根据自己的驾驶风格和环境背景,警报仅限于非常危险的情况,例如交通堵塞或降雪。
第二,在硬电击的情况下,警报以及在某些情况下,电击发生前 10 秒和电击发生后 5 秒的 15 秒视频被传送到专门的呼叫中心。根据这些信息,他们决定采取进一步的行动,比如报警或叫救护车。有了 GPS 数据,位置就知道了。这在夜间或非市区驾驶以及汽车偏离道路时尤其有用。可以节省宝贵的救援时间。
这种系统结合了实时数据、边缘计算、几秒钟内的精确动作建议、流数据、地理空间数据、时态数据和数据隐私的许多挑战。
工业制造 数字孪生是物理对象或过程的数字复制品。在工业制造过程中,每个过程阶段都由传感器和摄像机监控。所有数据都被传输到数字孪生机,并不断与目标值进行核对。分析偏差,并将反馈发送回机器,并基于边缘的机器学习算法进行调整和优化。
这种系统是数据驱动制造的高级艺术,集成了实时、动态、时态、大容量和稀疏数据,并结合了云计算和边缘计算。
还有更多物联网应用,如智能城市、能源部门、农业或交通。
物联网的数据科学需要什么?
虽然物联网世界中有许多令人兴奋的数据科学应用,但与传统的数据科学应用相比,它也面临着一些额外的挑战。这些示例表明,物联网数据科学家必须精通以下主题。
实时数据 数据从标记、结构化、聚合到分析都必须实时进行。结果必须实时可用。你需要不同的数据管理方法和技术来管理实时数据。
数据到洞察再到行动(反馈到系统) 光有来自数据的洞察是不够的。物联网系统需要对机器的给定动作进行反馈。洞察力必须能够被机器和/或人解释和理解,并立即明确地转化为行动。必须相应地设计流程,以便直接根据这些见解采取行动。
洞察和行动必须在数小时、数分钟和数秒内可用 自动化在每个数据科学流程阶段都至关重要。反馈和行动必须及时且持续地传输回物联网设备或在物联网设备中生成。没有时间进行人工干预。大多数情况下,这些动作是机器对机器交互的一部分。
数据是动态的 数据是实时收集和分析的。这意味着数据会实时动态变化。算法必须反映自动调整。这些模型必须整合从实时数据中进行的持续学习。
数据是时态的 所有数据都是时间相关的。模型和算法必须考虑到这一点。因此,通常时间序列分析和信号处理的方法是综合的。
数据量 很少有数据科学领域需要处理如此大量的数据。物联网设备产生的数据比社交媒体多。整个数据科学流程,从收集、预处理、算法分析,到生成见解和建议,都必须能够实时处理海量数据。
稀疏数据 即使数据量巨大,但大部分数据都是噪音。感兴趣的信号很少,通常很弱,如在事故发生前几周预测生产机器故障的异常,或与预期生产质量的微小偏差。找到这些见解并将其转化为准确的行动需要独特的方法来分析稀疏数据。
地理空间数据 不用说,地理空间数据的数量本身就是一个挑战。它链接了位置信息(三维)、时间信息和对象的属性。它有不同的校准标准,理解高维原始数据需要大量的高等数学知识。
边缘计算
边缘计算需要在设备上直接做出决策,例如向驾驶员或工人发出警报。这需要其他方法,这些方法可以利用有限的处理能力和知识来有效地在边缘和云计算之间进行划分。
需要高精度(安全性和成本敏感性) 洞察必须高度准确,并且假阳性和假阴性都应该很低。实时反馈给人和机器。错误的反馈会使工人面临危险,或导致机器或流程故障。
物联网设备敏锐度 每个传感器和设备都有自己的属性,例如数据的准确性和质量、传输技术(Wi-Fi、射频、蓝牙等。).由于技术的改进,三个月后制造的设备已经具有比三个月前制造的相同类型稍有不同的特性。获得准确的见解需要深入了解特定物联网设备的属性。
数据管理 物联网数据的数据管理存在各种挑战。从海量数据到不同传感器和设备的异构性、云和边缘之间的高效管理、所有数据科学阶段的即时处理,再到向设备提供反馈。数据管理系统必须服务于这一切。
缩放 更换或添加传感器会改变数据格式、精度、类型和容量。在开发这样的物联网生态系统之前,必须考虑和设计这些扩展方面。
网络安全 信息物理物联网系统需要针对网络攻击的适当保护。没有人希望整个生产工厂停工,或者在暗网上发布敏感的健康数据。这必须从一开始就整合,并且需要与网络安全专家密切合作。
数据隐私 许多国家都知道数据隐私法,尤其是健康数据。即使没有法律存在,保护客户数据不被滥用的内在利益仍然存在。导致声誉受损和客户流失会损害企业的盈利能力,导致诉讼和罚款。
行为经济学 对工人和人类的反馈和建议行动需要进行沟通、组织和激励,以改变行为,例如预防健康问题。许多研究表明,反馈只有在根据行为经济学原理构建时才会被接受和执行。
这些挑战显示了物联网数据科学的复杂性。所需的知识远远超出了通常的数据科学课程。
将点点滴滴连接起来
在物联网应用中担任数据科学家是数据科学的最高艺术。当你正在寻找你的下一个挑战,并希望提高你的数据科学技能时,我建议考虑进入物联网数据科学领域。
具有特定物联网分析技能的数据科学家需求量很大。在 LinkedIn 上,可以在美国和欧洲找到大约 8,500 个空缺职位。它提供了使用尖端方法和技术的机会。
让我们开始这激动人心的旅程。
你喜欢我的故事吗?
通过 加入我的电子邮件列表 , 每次我发布新的故事,你都会收到通知,通过成为媒介会员,你将拥有 访问数千篇鼓舞人心的文章 。
阅读下一篇: 从 24 小时黑客马拉松学到的防止 ML 项目失败的 5 点经验机器学习在医疗保健中应用的 10 个激动人心的例子作为数据科学初学者应该避免的 10 个错误
分位数回归模型简介
原文:https://towardsdatascience.com/introduction-to-the-quantile-regression-model-648a0532f534

图片来自 Pixabay ( Pixabay 许可)
我们将看看如何预测中位数和其他分位数点
在回归模型中,人们通常对估计响应变量的条件均值感兴趣。例如,考虑以下汽车价格与气缸数量的关系图:

价格与气缸数量。金色圆点代表条件均值: E(价格|汽缸数量)(图片由作者提供)(数据集: UCI ML 汽车用在 CC BY 4.0 )
金点代表观察到的基于圆柱体数量的平均价格。
如果我们想使用回归模型来估计这个条件平均价格,我们可以使用以下线性模型来完成这项工作:

估计汽车价格的线性模型(图片由作者提供)
如果我们将上述线性模型与数据拟合,并绘制其估计值,我们会得到以下图表:

经过训练的 OLS 模型的趋势线绘制在原始数据之上。红点是估计的条件平均价格,而金点是观察到的条件平均价格(图片由作者提供)
对于其他类型的数据,可以采用更复杂的模型,如泊松或考克斯比例风险模型。在所有情况下,我们都要求模型估计条件平均值。
但是在一些数据集中,平均值并不是数据的好样本。
以下是三个数据集示例,对于这些数据集,平均值可能不是合适的估计统计值:
- 数据严重向左或向右倾斜。在保险索赔或医疗保健费用数据中经常出现这种情况,其中大多数索赔金额较小,但数据中有一条价值不断增加的索赔长尾。
- 数据是多模态的,即它有一个以上的高频率出现值。例如,如果数据有两个大致相同的峰(双峰数据集),均值模型将估计两个峰之间的谷内点,该点不能很好地代表数据。
- 数据包含有影响的异常值。依赖于最小化残差平方和的普通最小二乘回归模型等回归模型对异常值的存在高度敏感。在这种情况下,除了而不是使用平均值作为要估计的量之外,最小化残差绝对值的和可能是有益的。
在所有这些情况下,平均值不足以代表数据的性质。估计条件中位数可能更好。
在最一般的术语中,我们的“寻找中值”回归模型将由以下等式指定:

一个回归模型,为某个值 X 估计 y 的条件中位数(图片由作者提供)
上式中, X 为回归矩阵,X_ I为矩阵的第行行。β_ cap是拟合回归系数和 f(.)是_ cap和x_ I的一些函数,用于估计约束条件下的概率估计值f(_ cap,x******
例如,用于估计汽车中间价格的模型,其中f(β_ cap、x_ I)具有线性形式将是:**

估算汽车中位价格的模型(图片由作者提供)
我们甚至可以更进一步。在数据集中,中位数是 0.5 分位数(或第 50 百分位)点,这意味着 50%的数据点小于中位数的值。类似地,还可以定义其他分位数点。0.1 分位数点(第 10 个百分点)是这样的值,即只有 10%的数据集小于该值。类似地,0.25 分位数点的值大于数据集的 25%,依此类推。
我们可能希望建立一个回归模型来估计任何或所有这些分位数点(或相应的百分位值)。
这种模型的一般方程如下:

q 分位数回归模型的一般方程(图片由作者提供)
上式中, Q(。)是 q 分位数(或第(q100)个百分点)的估计分位数点。如前所述,f(β_ cap,x_ I)是产生期望的 q 分位数点的估计值的函数,该函数受到 y 的任何观测值小于或等于估计值f(β的概率的约束 q 范围从 0 到 1,包括 0 和 1。*****
例如,用于估计汽车的第 95 百分位价格的模型,其中f(β_ cap、x_ I)具有线性形式将是:**

给定气缸数量,估算第 95 百分位汽车价格的模型(图片由作者提供)
分位数回归模型的用途是什么?
当数据在数据集的每个分位数中以不同的方式分布时,拟合不同的回归模型以满足每个分位数的独特建模需求,而不是尝试拟合预测条件均值的一刀切模型,可能会更有利。在这种情况下,所有这些分位数模型的系数将互不相同。
相反的情况是,数据在每个分位数中分布相同。具体地,估计条件均值的模型的误差项对于每个×_ I具有相同的分布。例如,所有x_ I的误差呈正态分布,并且所有分布的均值和方差都相同。换句话说,误差是同分位数 c。可以看出,在这种情况下,当不同的分位数回归模型拟合同分位数数据集时,所有模型的相应系数集将在统计上完全相同,并且各种分位数模型仅在回归截距的值上彼此不同。事实上,这种行为可以构成异方差测试的基础。**
建立分位数模型的另一个原因是,我们实际上对估计一个特定的分位数感兴趣,比如说,社会经济原因。例如,我们可能想要估计具有相同人口统计学特征的考生的第 95 百分位 SAT 分数。
在下一节中,我们将浏览一个简短的教程,学习如何使用 Python 构建分位数模型。
如何使用 Python 和 statsmodels 构建分位数回归模型
我们将使用以下车辆数据集来说明构建分位数回归模型的过程,该数据集包含取自 1985 年版的沃德汽车年鉴的 200 多辆汽车的规格。每行包含一组 26 个关于单个车辆的规格:

汽车数据集(来源:https://archive-beta.ics.uci.edu/ml/datasets/automobileCC BY 4.0下加州大学欧文分校
这些数据的子集可以从这里 下载 。
让我们将数据集加载到熊猫数据框架中,并绘制数据集。
***import** pandas **as** pd
**import** statsmodels.api **as** sm
**import** statsmodels.formula.api **as** smf
**from** patsy **import** dmatrices
**import** numpy **as** np
**from** matplotlib **import** pyplot **as** plt***#Import the 7-variable subset of the automobiles dataset into a DataFrame***df = pd.**read_csv**(**'automobiles_dataset_subset_uciml.csv'**, **header**=0)***#Plot price versus num_of_cylinders*** fig = plt.**figure**()fig.**suptitle**(**'Price versus Number of Cylinders'**)plt.**xlabel**(**'number_of_cylinders'**)
plt.**ylabel**(**'price'**)plt.**scatter**(**x**=df[**'num_of_cylinders'**], **y**=df[**'price'**])*
让我们也画出条件均值 E(价格|气缸数量):
*num_of_cylinders = np.**array**(df.**groupby**('num_of_cylinders')['num_of_cylinders'].**mean**())conditional_means = np.**array**(df.**groupby**('num_of_cylinders')['price'].**mean**())
plt.scatter(**x**=num_of_cylinders, **y**=conditional_means, **color**='gold', **marker**='o')
plt.**show**()*
我们得到了下图。金点代表有条件的意思:

价格与气缸数量。金点代表条件均值: E(价格|缸数)(图片由作者提供)
接下来,让我们用 Patsy 语法定义回归模型的方程。截距假定为:
*reg_exp = **'price ~ num_of_cylinders'***
我们将使用这个表达式来雕刻出 y 和 X 矩阵:
*y_train, X_train = **dmatrices**(reg_exp, df, **return_type**='dataframe')*
我们将首先建立并训练一个普通的最小二乘(OLS)回归模型,该模型为给定的气缸数量值估计条件平均价格,并在散点图上绘制回归线:**
****#Build and train an OLS regression model***
olsr_model = sm.**OLS**(**endog**=y_train, **exog**=X_train)
olsr_model_results = olsr_model.**fit**()
**print**(olsr_model_results.**summary**())
***#Plot the OLS regression line on the scatter plot of Price versus num_of_cylinders*** fig = plt.**figure**()
fig.**suptitle**('Price versus Number of Cylinders')
plt.**xlabel**('number_of_cylinders')
plt.**ylabel**(**'**price**'**)
plt.**scatter**(**x**=df['num_of_cylinders'], **y**=df['price'])***#Get the estimated conditional means from the trained OLS model*** y_pred_ols = olsr_model_results.**predict**(X_train)***#Plot the estimated conditional means*** ols, = plt.**plot**(X_train['num_of_cylinders'], y_pred_ols,
**color**=**'**red**'**, **marker**=**'**o**'**, **linestyle**=**'**solid**'**, **label**=**'**OLS Model**'**)***#Also plot the observed conditional means i.e. E(price | num_of_cylinders)*** conditional_mean_pts = plt.**scatter**(**x**=num_of_cylinders, **y**=conditional_means, **c**=**'**gold**'**, **marker**=**'**o**'**, **label**='E(price | num_of_cylinders)')
plt.**legend**(**handles**=[ols, conditional_mean_pts])plt.**show**()*
我们看到下图:

经过训练的 OLS 模型的趋势线绘制在原始数据之上。红点是估计的条件平均价格,而金点是观察到的条件平均价格(图片由作者提供)
现在让我们开始处理中位数和其他分位数点。
我们将首先训练一个模型,该模型将估计条件中值,即中值(price|num_of_cylinders) 。**
让我们创建一个分位数回归模型的实例,如下所示:
*median_model = smf.**quantreg**(**formula**=reg_exp, **data**=df)*
接下来,我们将训练模型。我们将告诉 statsmodels 我们希望符合条件中位数,即 0.5 分位数点:
*median_model_results = median_model.**fit**(**q**=0.5)*
现在,让我们在原始价格对气缸数量数据的背景下,绘制该模型的估计条件中值点。为了进行比较,我们还将绘制我们之前构建的 OLS 模型的估计条件均值,并且我们还将绘制条件均值和条件中值的观察值以进行比较。**
下面这段 Python 代码完成了所有这些工作:
*fig = plt.figure()fig.suptitle(**'Price versus Number of Cylinders'**)plt.xlabel(**'number_of_cylinders'**)
plt.ylabel(**'price'**)***#Show the scatter plot of price versus num_of_cylinders*** plt.**scatter**(**x**=df[**'num_of_cylinders'**], **y**=df[**'price'**], **c**=**'deepskyblue'**)***#Get the estimated conditional medians from the median model*** y_pred_median = median_model_results.**predict**(X_train)***#Plot the estimated conditional medians*** median, = plt.plot(X_train[**'num_of_cylinders'**], y_pred_median,
color=**'black'**, marker=**'o'**, linestyle=**'solid'**, label=**'Median Model'**)***#For comparison, also plot the estimated conditional means from the OLS model we built earlier*** ols, = plt.plot(X_train[**'num_of_cylinders'**], y_pred_ols,
color=**'red'**, marker=**'o'**, linestyle=**'solid'**, label=**'OLS Model'**)***#Calculate the observed conditional medians*** conditional_medians = np.array(df.**groupby**(**'num_of_cylinders'**)[**'price'**].median())***#Plot the observed conditional medians*** conditional_median_pts = plt.scatter(x=num_of_cylinders, y=conditional_medians, c=**'sienna'**, marker=**'^'**, label=**'Median(price | num_of_cylinders)'**)***#For comparison, plot the observed conditional means*** conditional_mean_pts = plt.scatter(x=num_of_cylinders, y=conditional_means, c=**'gold'**, marker=**'o'**, label=**'E(price | num_of_cylinders)'**)***#Set up the legend and show the plot*** plt.legend(handles=[ols, median, conditional_mean_pts, conditional_median_pts])
plt.show()*
我们看到下面的情节:

来自 OLS 模型的预测条件均值(红色)和来自分位数模型的条件中位数(黑色),叠加在原始数据上的观察条件均值(金色圆点)和观察条件中位数(黑色三角形)(图片由作者提供)
我们从图中注意到的一点是,条件中位数和条件均值点的观察值几乎相互重叠,这意味着价格数据在均值附近或多或少是平衡的。OLS 和中位数回归模型的趋势线证明了这一点,它们彼此非常接近,尤其是朝着数据的中心部分。
让我们也为其他几个分位数点绘制回归线,比如,[0.1,0.25,0.5,0.75,0.9]。
*fig = plt.**figure**()fig.**suptitle**('Price versus Number of Cylinders')plt.**xlabel**('number_of_cylinders')
plt.**ylabel**('price')plt.**scatter**(**x**=df[**'**num_of_cylinders**'**], **y**=df[**'**price**'**])coeff = []
colors = [**'orange'**, **'lime'**, **'yellow'**, **'cyan'**, **'violet'**]
i=0
handles = []
quantiles = [0.1, 0.25, 0.5, 0.75, 0.9]**for** q **in** quantiles:
***#Build the model***quantile_model = smf.**quantreg**(**formula**=reg_exp, **data**=df) ***#Fit the model***quantile_model_results = quantile_model.**fit**(**q**=q) **print**(quantile_model_results.**summary**()) coeff.**append**(quantile_model_results.**params**[**'**num_of_cylinders**'**]) ***#Get the estimated values from the quantile model***y_pred_quantile = quantile_model_results.**predict**(X_train) ***#Plot the estimated values***quantile, = plt.**plot**(X_train[**'**num_of_cylinders**'**], y_pred_quantile, **color**=colors[i], **marker**=**'**o**'**, **linestyle**=**'**solid**'**, **label**=str(int(q*100))+'th percentile Model') i = i+1
handles.**append**(quantile)
***#Also plot the estimated values from the OLS model for comparison*** ols, = plt.plot(X_train[**'num_of_cylinders'**], y_pred_ols,
color=**'black'**, marker=**'o'**, linestyle=**'solid'**, label=**'OLS Model'**)
handles.**append**(ols)
plt.**legend**(**handles**=handles)plt.show()*
下面是这个情节的样子:

各种分位数模型的预测图叠加在原始数据上(天蓝色)。OLS 模型的预测(黑色)供参考(图片由作者提供)
如果来自 OLS 模型的误差是同分布的,换句话说,如果误差是同分布的,所有分位数模型的趋势线将仅在截距上不同,即它们将彼此平行。这显然不是我们在这种情况下看到的,让我们相信 OLS 模型的误差是异方差的。
气缸数的估计系数相对于分位数的曲线得出以下曲线,该曲线仅加强了上述结论:

各种分位数模型的估计气缸数系数与分位数的关系图(图片由作者提供)
我们注意到的另一件事是,气缸数对汽车价格的部分效应(即气缸数的系数)随着分位数的增加而增加,表明较高的分位数价格比较低的分位数价格对气缸数的变化更敏感。例如,与第 80 百分位价格相比,第 90 百分位车辆价格随着气缸数量的每一单位变化而变化更多。**
下面是本文中使用的完整源代码:
参考文献、引文和版权
数据集
汽车数据集由 4.0 来源于 CC 下的 UCI ML 数据集库。
形象
本文中所有图片的版权归 CC-BY-NC-SA 所有,除非图片下面提到了不同的来源和版权。
如果你喜欢这篇文章,请关注我的Sachin Date获取关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。**
两步最小二乘估计简介
原文:https://towardsdatascience.com/introduction-to-two-stage-least-squares-estimation-4dbabdd98102

图片由 SpaceX-Imagery 发自 Pixabay ( 牌照)
我们将学习如何使用 2SLS 技术来估计包含工具变量的线性模型
在本文中,我们将学习使用工具变量技术估计线性模型的两种不同方法。
在上一篇文章中,我们学习了工具变量,它们是什么,何时以及如何使用它们。让我们回顾一下我们所学的内容:
考虑以下线性模型:

二元线性模型 x _2 和 x _3(图片由作者提供)
上式中,,* 1,x _2,x_ 3,【ϵ为大小为【n×1】的列向量。从后面的等式中,为了简洁起见,我们将去掉 1 (它是 1 的向量)。*
如果一个或多个回归变量,比如说x_ 3,是**,即它与误差项相关,则普通最小二乘(OLS)估计量是不 一致 。它生成的系数估计值偏离了真实值,使人对实验的有用性产生疑问。****
挽救这种局面的一个办法是想出一个办法,有效地将×3_ 3分成两部分:**
- 与 ϵ 不相关的块,我们将把它添加回模型中,以代替x_ 3。这是x_ 3的部分,其实是外生的。**
- 与 、ϵ 相关的第二个块,我们将从模型中切掉。这是内生的部分。
而实现这个目标的一个方法就是标识一个变量**_ 3,“一个用于x***_ 3”,具有以下属性:*****
- 与 x _3 相关。认为(在某种程度上)满足了上述两个要求中的第一个,并且
- 它与满足第二个要求的误差项无关。
将x_ 3替换为z_ 3产生以下模型:**

内生变量 x _3 替换为变量 z _3 的线性模型(图片由作者提供)
等式(1a)的 R.H.S 上的所有变量都是外生的。该模型可以使用最小二乘法进行一致估计。
上述估计技术可以很容易地扩展到多个内生变量及其相应的工具,只要每个内生变量与单个唯一的工具变量一对一配对。**
上面的例子给出了 IV 估计的一般框架,我们将在下面介绍。
y 对 X 的线性回归采用如下矩阵形式:

线性模型,其中 y 在 X 上回归(图片由作者提供)
假设数据集的大小为 n ,在等式(2)中:
- y 是大小【n×1】的向量。**
- X 是大小为【n X(k+1)】的回归变量的矩阵,即它有 n 行和 (k+1) 列,其中第一列是一列 1,作为截距的占位符。**
- β 是大小为 [(k+1) x 1] 的回归系数的列向量,其中第一个元素 β_1 是回归的截距。
- ϵ 是回归误差大小【n x1】的列向量。有效保存了中模型 Xβ 无法解释的差额余额。******
下面是上述等式的矩阵格式:

线性回归模型的矩阵版本(图片由作者提供)
不失一般性,且不计截距,让我们假设 X 中的第一个 p 回归变量是外生的,下一个 q 变量是内生的,使得 1 + p + q = k :

由 p 个外生变量和 q 个内生变量组成的 X 矩阵(图片由作者提供)
假设我们能够识别出 q 工具变量,这些工具变量将是 X 中相应的 q 回归变量的工具,即X_(p+1)到X_ k中被怀疑是内生的变量。****
让我们构造一个矩阵 Z 如下:
- Z 的第一列将是 1 的一列。
- 下一个 p 列的 Z 即Z_ 2到Z_ p将与 p 外生变量x_ 2到 x 相同**
- 中的最后一组 q 列,即Z【p+1】到Z*_ k将保存 q 变量的数据,这些变量将作为相应q的工具***
因此, Z 的大小也是【n X(k+1)】,即与 X 的大小相同。**

Z 矩阵(图片由作者提供)
接下来,我们将采用转置 Z 来交换行和列。转置操作实质上是将 Z 翻转过来。 Z 的转置记为Z’大小为 [(k+1) x n]。**
现在,让我们将等式(2)与Z’预先相乘:**

将等式(2)与Z’相乘(图片由作者提供)
等式(3)在尺寸上是正确的。在左侧,的大小为[(k+1)x n]y的大小为【n x 1】*。因此 Z'y 的大小为 [(k+1) x 1] 。*****
在右侧, X 的尺寸为【n X(k+1)】β的尺寸为[(k+1)X 1】。从左到右, Z'X 是一个大小为[(k+1)x(k+1)】和(Z ' x)的大小为 [(k+1) x 1】。**
同样, ϵ 大小为【n×1】。所以 Z'ϵ 的大小也是 [(k+1) x 1]。**
现在,让我们应用期望操作符 E(。)情商两边。(3):

将期望算子应用于等式(3)的两边(图片由作者提供)
**E(Z ' y)和E(Z ' xβ)分别解析为 Z'y 和 Z'Xβ 。
回忆一下 Z 只包含外生变量。因此,【z】和不相关,因此(【z'ϵ)*的平均值是零的列向量,等式(3a)解析为:*****

(图片由作者提供)
接下来,我们将通过将等式(4)的两边乘以方阵的逆矩阵(Z ' x***)来分离等式(4)的 R.H.S .上的系数向量 β 。*****
矩阵的逆在概念上是标量数 N 的逆的多维等价(假设 N 不为零)。矩阵的逆矩阵是用一个复杂的公式计算的,我们将跳过这个公式。**
有可能表明( Z'X )是可逆(同样这点我们不会在这里深究)。等式两边的预乘。(4)由(z ' x)的逆即(z ' x,得到如下结论:****

R.H.S .上的黄色位和绿色位相互抵消,以与 N(1/N)等于 1 相同的方式产生一个单位矩阵,留给我们以下等式来估计仪表模型的系数向量 β :*

计算包含工具变量的回归模型的回归系数的公式(图片由作者提供)
请注意, Z 、 X 和 y 都是可观测的量,因此只要×中的内生变量与【Z中选择的工具一一对应,就可以使用等式(6)一次性估计所有回归系数。******
关于等式(6),还有最后一点必须提及。严格地说,等式(6)仅可渐近估计,即当数据样本的数量 n → ∞ 时。但是在实践中,由于一系列数学原因,我们可以用它来计算一个模型的系数估计值,这个模型是通过 IV 对有限大小的样本进行估计的,换句话说,是对真实世界的数据集进行估计的。
因此,的有限样本 IV 估计量 β_cap_IV 可表述如下:**

含 IVs 模型回归系数的有限样本估计量(图片由作者提供)
现在,让我们看看内生变量定义了多个工具变量的情况。
考虑以下工资回归模型:

根据各种变量回归的工资记录(图片由作者提供)
在上面的模型中,我们回归了 工资 的自然对数,而不是原始工资,因为工资数据通常是右偏的,记录它可以减少偏斜。 学历 是以受教育年限来衡量的。 学院 和 城市 是布尔变量,表示这个人是否上过大学,是否居住在一个城市。 Unemp 包含居住县的失业率百分比。
我们的 X 矩阵是1年龄经历学院城市T53************
我们会认为 教育 是内生的。因此,学校教育年数只掌握了在学校或大学里教授的内容。它还忽略了一些方面,如这个人对材料的掌握程度,他们对课程之外的主题的了解程度等等,所有这些都没有被观察到,因此在错误术语中被捕捉到。
我们将提出两个变量,母亲的受教育年数()和父亲的受教育年数(feeducation)作为个人的的 IVs。******
关联性和外生性条件
我们选择的 iv 需要通过关联条件。如果对 教育 进行一次回归,对其余变量进行X教育** 和 教育 揭示(通过一次 F 检验 )即 教育 和 教育******
误差项 ϵ 本来就是不可观测的。因此不能直接检测 IVs 的外源性条件。相反,我们相信父母的受教育年限不太可能与孩子对材料的掌握等因素相关,即隐藏在错误术语中并使教育成为内生的因素。但是我们可能错了。我们很快就会知道了。
包含 IVs 的回归模型
我们的 IVs 回归模型如下:

各种变量的工资回归记录,包括两个变量:教育和非教育(图片由作者提供)
我们的矩阵是1年龄经验**学院 其中每个变量是大小为【n×1】的列向量,大小为的列向量是【n×8】。 注意我们是如何用它的两个 IVs 取代了 education。**************
并且待估计的系数向量是:
β_ cap _ IV =[β _ 1 _ cap,β_2_cap,β_3_cap,β_4_cap,β_5_cap,β_6_cap,β_7_cap,β*_8_cap]*****
其中大写字母表示估计值。
定义了 X 和 Z 之后,是否可以用 Eq (6a)对β_ cap _ IV***进行单次计算?*****

含 IVs 模型回归系数的有限样本估计量(图片由作者提供)
很遗憾,答案是,没有。
回想一下 ZT3 的大小 是【n×8】。所以, Z' 的大小为【8 x n】。 X 的大小为【n X 7】。因此Z’x的尺寸【8×7】不是方阵,因此 不可逆 。于是,Eq。(6a)当多个工具变量如教育 用于代表单一内生变量如时,不能使用。********
这个困难建议我们探索一种不同的方法来估计 β_cap_IV。 这种不同的方法是两阶段 OLS 估计量。
两步 OLS 估计量
我们从开发这个估计器的第一阶段开始。
第一阶段
在这个阶段,我们将倒退 学历年龄阅历学院城市* ,*******
假设我们已经通过 f 检验确定 教育 确实与 IVs 教育 和 教育 相关。
我们现在将回归 教育 不仅关于 教育 和 教育 而且关于其他变量,这允许我们考虑非 IV 变量和 IV 变量之间可能的相关性的影响。参见我之前关于工具变量的文章,了解这种效应的详细解释。

教育在 Z 矩阵上退步(图片由作者提供)
ν 是误差项。由于所有回归变量都是外生的,因此可以使用 OLS 对上述模型进行一致估计。估计模型具有以下形式:

拟合模型后教育的估计值,该模型在 Z 矩阵上回归教育(图片由作者提供)
在上面的等式中, education_cap 是 education 的估计值(又称预测值)。系数上的上限同样表示估计值。
上述基于 OLS 的回归代表了我们即将进行的两阶段 OLS (2SLS)估算的第一阶段。
第二阶段
关于第一阶段的关键见解是education _ cap仅包含education的方差的一部分,即外生的,即与错误项不相关。
因此,我们可以将原模型中的 ln( 工资 ) 替换为education _ cap,形成一个只包含外生回归变量的模型,如下:**

工资日志在 X 回归,用其外生部分替换学历(图片由作者提供)****
由于上述模型仅包含外生回归变量,因此可以使用 OLS 对其进行一致估计。该估计形成了 2 级 OLS 估计器的第二级。
2sl 的总体框架
对于那些对线性代数有天赋的人来说,2 阶段最小二乘法的一般框架如下(如果你愿意,你可以跳过这一节直接进入 Python 教程 2sl):
让我们开始第一阶段的工作。
第一阶段
在第一阶段,我们估计以下模型。为了保持事物的一般性, X 不仅包含内生教育,还包含其余变量, γ 是回归系数的向量, ν 是误差项:

X 对 Z 的线性回归(图片由作者提供)
γ 的最小二乘估计量可以显示为使用基于最小二乘的估计量的标准公式计算如下:

γ 的 OLS 估计量(图片由作者提供)
使用 γ _cap, X 的估计值由下式给出:

使用估计的系数γ_ cap【图片由作者提供】估计 X**
这就完成了 2-SLS 的第一阶段。
现在,让我们开始第二阶段的工作。
第二阶段
让我们回忆一下等式 6(a ),它是我们针对中的内生变量与中的工具之间存在一一对应关系的情况构建的 IV 估计量:****

含 IVs 模型回归系数的有限样本估计量(图片由作者提供)
我们将等式(6c)中的X***_ cap代入等式(6a)中的 Z 得到_ cap _ 2 SLS如下:*****

使用 2 阶段最小二乘法估算的仪器模型的系数(图片由作者提供)
这就完成了 2-SLS 估计器的公式化。实验者完全可以观察到方程(6b)的 R.H.S .上的所有矩阵。系数的估计可以通过简单地按顺序应用等式(6bb)、(6c)和(6d)来实现:

二阶段最小二乘估计器(图片由作者提供)
使用 Python 和 statsmodels 使用 2SLS 估计线性模型的教程
我们将使用 1976 年基于前一年 1975 年数据的已婚妇女收入动态研究的截面数据。

PSID76 数据集(来源: R 数据集在 GPL v3 下)
每行包含一个已婚女性参与者的时薪数据和其他变量。数据集包含几个变量。我们感兴趣的问题如下:
**工资:1975 年平均小时工资
学历:参与者的受教育年限
学历:参与者母亲的受教育年限
学历:参与者父亲的受教育年限
参与度:个人是否于 1975 年参加劳动?(1/0).我们只考虑那些在 1975 年参加的人。
我们的目标是估计教育对 1975 年已婚女性受访者小时工资的影响,具体来说是小时工资的对数。

估计教育对小时工资日志影响的模型(图片由作者提供)
正如我们之前看到的,教育是内生的,因此使用 OLS 的直接估计将产生所有系数的有偏估计。具体来说,OLS 对 β_1 和 β_2 的估计可能会高估它们的值,也就是说,它会高估教育对时薪的影响。
我们将尝试通过使用教育和教育作为教育的工具来补救这种情况。****
我们将使用 Python、 Pandas 和 Statsmodels 来加载数据集并构建和训练模型。让我们从导入所需的包开始:
****import** pandas **as** pd
**import** numpy **as** np
**import** statsmodels.formula.api **as** smf
**from** statsmodels.api **import** add_constant
**from** statsmodels.sandbox.regression.gmm **import** IV2SLS**
让我们将数据集加载到熊猫Dataframe:
**df = pd.**read_csv**(**'PSID1976.csv'**, **header**=0)**
接下来,我们将使用数据集的一个子集,其中 participation=yes。
**df_1975 = df.**query**('participation == \'yes\'')**
我们需要验证仪器教育和教育满足相关条件。为此,我们将对教育和教育的教育进行回归,并使用 f 检验验证教育和教育在此回归中的系数和共同显著。****
**reg_expr = **'education ~ meducation + feducation'**olsr_model = smf.**ols**(**formula**=reg_expr, **data**=df_1975)olsr_model_results = olsr_model.**fit**()**print**(olsr_model_results.**summary**())**
我们看到以下输出:

线性模型的训练摘要,该模型对教育、教育和常数进行回归(图片由作者提供)
教育和教育的系数在<的 p 为 0.001 时各自显著,如其 p 值基本为零所示。这些系数在 p 为 2.96e-22 时也是共同显著的,即< .001。教育和教育明显满足教育*的 IVs 的关联条件。***
我们现在将为工资方程建立一个线性模型,并使用 statsmodels,我们将使用 2SLS 估计量来训练该模型。
我们将从构建设计矩阵开始。因变量为 ln(工资):
**ln_wage = np.**log**(df_1975['**wage**'])**
Statsmodel 的 IV2SLS 估计量定义如下:
statsmodels.sandbox.regression.gmm.IV2SLS(***endog***, ***exog***, ***instrument****=None*)
Statsmodels 需要以如下特定方式构建***endog***、***exog***和***instrument***矩阵:
***endog***是包含因变量的【n×1】矩阵。在我们的示例中,它是 ln_wage 变量。**
***exog***是一个【n x(k+1)】大小的矩阵,它必须包含所有的内生变量和外生变量,加上常数。在我们的例子中,除了常数之外,我们没有在工资方程中定义任何外生变量。所以它看起来像这样:**

***instrument***是包含工具变量的矩阵。此外,Statsmodels 的IV2SLS估计器要求instrument也包含exog矩阵中未被检测的的所有变量。在我们的例子中,工具变量是教育和教育。exog中的变量是而不是被检测的,它们只是截距的占位符列。因此,我们的仪器矩阵将如下所示:****

让我们构建三个矩阵:
**df_1975[**'ln_wage'**] = np.log(df_1975[**'wage'**])exog = df_1975[[**'education'**]]
exog = **add_constant**(exog)instruments = df_1975[[**'meducation'**, **'feducation'**]]
instruments = **add_constant**(instruments)**
现在让我们构建并训练IV2SLS模型:
**iv2sls_model = **IV2SLS**(**endog**=df_1975[**'ln_wage'**], **exog**=exog, **instrument**=instruments)iv2sls_model_results = iv2sls_model.**fit**()**
让我们打印培训总结:
****print**(iv2sls_model_results.**summary**())**

2SLS 模型的训练总结(图片由作者提供)
2SLS 模型结果的解释
由于我们的主要兴趣是估计教育对小时工资的影响,我们将把注意力集中在教育变量的系数估计上。
我们看到 2SLS 模型已经估计出教育的系数为 0.0505,标准差为 0.032,95% 置信区间为-0.013 到 0.114。p 值为 0.117 表明显著性为(1–0.117)100% = 88.3%。总的来说,正如 2-SLS 模型所预期的那样,该模型缺乏精确性。**
注意因变量是 log (工资)。要计算教育每单位变化(即一年)的小时工资变化率,必须对教育的系数取指数。**
e^(0.0505)=1.05179 暗示,教育年数每增加一个单位,小时工资估计会增加 1.05179 美元,反之亦然。
IV 估计量与 OLS 估计量的比较
让我们比较一下 2SLS 模型和简单的 OLS 模型的表现,后者回归了关于教育的 log(工资)T1 和 T2 T3。
**reg_expr = **'ln_wage ~ education'**olsr_model = smf.**ols**(**formula**=reg_expr, **data**=df_1975)olsr_model_results = olsr_model.**fit**()**print**(olsr_model_results.**summary**())**

OLS 模式的培训总结(图片由作者提供)
我们将关注教育系数的估计值。0.1086,是 2SLS 模型报告的估计值的两倍。**
e^(0.1086)=1.11472,这意味着教育年数的单位增加(减少)估计会转化为每小时工资增加(减少)1.11472 美元。
由于怀疑教育的内生性,预计 OLS 的估计值会更高。实际上,根据我们正在建模的情况,我们可能希望接受由 2SLS 模型报告的更保守的估计值 0.0505。然而,(相对于 2SLS 模型),OLS 模型的系数估计非常显著,p 值基本为零。回想一下,来自 2SLS 模型的估计仅在 88%的置信水平下是显著的。
此外,(再次如 OLS 模型所预期的那样),OLS 模型报告的教育的系数估计值的标准误差(0.014)比 2SLS 模型的标准误差(0.032)小得多。因此,来自 OLS 模型的相应的 95% ci 比由 2SLS 模型估计的那些要紧密得多。**
为了比较,下面是两个模型中教育和相应 95%置信区间的系数估计值:**

2sl 和 OLS 模型报告的教育系数估计值的比较(图片由作者提供)
使用 IV 估计量,可以用估计精度来换取估计中内生性和随之而来的偏差的消除。
下面是两个模型估算的教育的 主效应 对时薪的比较:**

教育对 2sl 和 OLS 模型估算的时薪的主要影响的比较(图片由作者提供)
数据集和教程代码
文章中使用的工资数据集可以通过此链接访问。相关文件可以在这里找到。
下面是本文中显示的完整源代码:
相关阅读
**** ****
参考文献、引文和版权
数据集
劳动力参与数据集是 R 数据集的一部分。Vincent Arel-Bundock 根据 GPL-3 许可将其作为 Rdatasets 包的一部分提供。
形象
本文中的所有图片版权归 Sachin Date 所有,版权归 CC-BY-NC-SA 所有,除非图片下面提到了不同的来源和版权。
如果您喜欢这篇文章,请关注我的Sachin Date以获得关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。
Word2Vec 简介(跳过程序)
原文:https://towardsdatascience.com/introduction-to-word2vec-skip-gram-cb3e9533bcf1
NLP 基础
Python 中的单词嵌入简介
当处理文本数据时,我们需要将文本转换成数字。用数字数据表示文本有不同的方式。单词包(又名 BOW)是一种用数字表示文本的流行而简单的方法。然而,单词包中没有单词相似性的概念,因为每个单词都是独立表示的。因此,像、【太棒了】、、【太棒了】、这样的单词的嵌入彼此之间的相似性,就像它们与单词、【书】、的嵌入一样。
单词嵌入是用数字表示文本的另一种好方法。使用这种方法,每个单词都由一个嵌入的密集向量(即一个数字数组)来表示。该方法保留了单词之间的关系,并且能够捕获单词相似性。出现在相似上下文中的单词在向量空间中具有更近的向量。因此,单词‘伟大’很可能比‘书’有更多与‘牛逼’相似的嵌入。

由 Sebastian Svenson 在 Unsplash 上拍摄的照片
在本文中,我们将对单词嵌入进行概述,尤其是一种称为 Word2Vec 的嵌入算法,并深入了解该算法如何在 Python 中的一个玩具示例上运行。
📜Word2Vec (Skipgram)概述

图片作者|预处理一个示例文档的对比:“Hello world!”有两种方法。假设单词包方法的词汇量为 5,单词嵌入的嵌入量为 3。
当使用单词包方法时,我们通过 n 将文本转换成一个 m 的文档术语矩阵,其中 m 是文档/文本记录的数量,而 n 是所有文档中唯一单词的数量。这通常会产生一个很大的稀疏矩阵。如果你想详细了解这种方法,请查看本教程。
在单词嵌入中,每个单词由一个向量表示,通常大小为 100 到 300 。Word2Vec 是一种创建嵌入的流行方法。Word2Vec 背后的基本直觉是这样的:我们可以通过观察一个单词的上下文/邻居来获得关于它的有用信息。在 Word2Vec 中,我们可以使用两种架构或学习算法来获得单词的矢量表示(只是另一个用于嵌入的单词):C 连续单词包(又名 CBOW) 和 S kip-gram 。
◼️ CBOW: 预测焦点词给定周边上下文词t17】◼️跳跃式:预测上下文词给定焦点词(本文的重点)
在现阶段,这可能没有太大意义。我们将很快看到一个例子,这将变得更加清楚。
当使用 Skip-gram 算法训练嵌入时,我们在高层次上经历以下三个步骤:
◼️ 获取文本:我们从未标记的文本语料库开始——所以这是一个无监督的学习问题。
◼️ 变换数据:然后,我们对数据进行预处理,并将预处理后的数据重新排列成作为特征的焦点词和作为虚拟监督学习问题的目标的上下文词。所以,它变成了一个多分类问题,其中 P(上下文词|焦点词)。下面是一个在单个文档中可能出现的情况的示例:

作者图片|我们首先将文本预处理成标记。然后,对于作为焦点单词的每个标记,我们找到窗口大小为 2 的上下文单词。这意味着我们将焦点单词前后的两个标记视为上下文单词。我们可以看到,在这样一个小的示例文本中,并不是所有的标记前后都有 2 个标记。在这些情况下,我们使用可用的令牌。在这个例子中,我们不严格地、可互换地使用术语单词和令牌。
同一个特性有多个目标可能会让人难以想象。以下是考虑如何准备数据的另一种方式:

作者图片
本质上,我们准备特征和目标对。
◼️ 建立一个简单的神经网络:然后,我们使用新构建的数据集为监督学习问题训练一个具有单个隐藏层的简单神经网络。我们训练神经网络的主要原因是从隐藏层获得训练的权重,该隐藏层成为单词嵌入。出现在相似上下文中的单词的嵌入倾向于彼此相似。
概述完之后,是时候用 Python 实现它来巩固我们所学的内容了。
🔨Python 中的 Word2Vec
由于这篇文章的重点是开发算法如何工作的更好的直觉,我们将专注于自己构建它,而不是使用预先训练的 Word2Vec 嵌入来加深我们的理解。
🔗免责声明:在开发这篇文章的代码时,我大量使用了以下资源库:
◼️ 单词嵌入创建作者埃利吉尤斯 112 (他的媒体页面:埃利吉尤斯布约卡斯)
◼️word 2 vec _ numpy作者 DerekChia我要感谢这些了不起的作者让他们的有用的工作为他人所用。如果您想加深对 word2vec 的理解,他们的存储库是很好的额外学习资源。
🔨带 Gensim 的 Word2vec
经过他的允许,我们将使用来自 Eligijus112 知识库的这个样本玩具数据集。让我们导入库和数据集。
import numpy as np
import pandas as pd
from nltk.tokenize.regexp import RegexpTokenizer
from nltk.corpus import stopwords
from gensim.models import Word2Vec, KeyedVectors
from scipy.spatial.distance import cosineimport tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Denseimport matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid', context='talk')text = ["The prince is the future king.",
"Daughter is the princess.",
"Son is the prince.",
"Only a man can be a king.",
"Only a woman can be a queen.",
"The princess will be a queen.",
"Queen and king rule the realm.",
"The prince is a strong man.",
"The princess is a beautiful woman.",
"The royal family is the king and queen and their children.",
"Prince is only a boy now.",
"A boy will be a man."]
我们现在将非常轻松地对文本进行预处理。让我们创建一个函数,它将文本小写,将文档标记为字母标记,并删除停用词。

作者图片
预处理的级别可以随实现的不同而不同。在一些实现中,可以选择做很少的预处理,保持文本几乎原样。另一方面,你也可以选择做一个比这个例子更彻底的预处理。
def preprocess_text(document):
tokeniser = RegexpTokenizer(r"[A-Za-z]{2,}")
tokens = tokeniser.tokenize(document.lower())
key_tokens = [token for token in tokens
if token not in stopwords.words('english')]
return key_tokens
corpus = []
for document in text:
corpus.append(preprocess_text(document))
corpus

作者图片
现在每个文档都由令牌组成。我们将在自定义语料库上使用 Gensim 构建 Word2Vec:
dimension = 2
window = 2word2vec0 = Word2Vec(corpus, min_count=1, vector_size=dimension,
window=window, sg=1)
word2vec0.wv.get_vector('king')

作者图片
我们为上下文选择大小为 2 的window。这意味着我们将在焦点标记之前和之后查看 2 个标记。dimension也被设置为 2。这是指向量的大小。我们选择 2 是因为我们可以很容易地在二维图表中将其可视化,并且我们正在使用一个非常小的文本语料库。这两个超参数可以用不同的值来调整,以提高用例中单词嵌入的有用性。在准备 Word2Vec 时,我们通过指定sg=1来确保使用 Skip-gram 算法。一旦嵌入准备就绪,我们就可以看到令牌的嵌入'king'。
让我们看看嵌入有多直观。我们会挑选一个样本词:'king',看看向量空间中与它最相似的词是否有意义。让我们找出与'king'最相似的 3 个词:
n=3
word2vec0.wv.most_similar(positive=['king'], topn=n)

作者图片
这个元组列表显示了与'king'最相似的单词及其余弦相似度。鉴于我们使用的数据非常少,这个结果还不错。
让我们为词汇表准备一个嵌入的数据框架,这是唯一标记的集合:
embedding0 = pd.DataFrame(columns=['d0', 'd1'])
for token in word2vec0.wv.index_to_key:
embedding0.loc[token] = word2vec0.wv.get_vector(token)
embedding0

作者图片
现在,我们将可视化二维向量空间中的记号:
sns.lmplot(data=embedding0, x='d0', y='d1', fit_reg=False, aspect=2)
for token, vector in embedding0.iterrows():
plt.gca().text(vector['d0']+.02, vector['d1']+.03, str(token),
size=14)
plt.tight_layout()

作者图片
🔗如果你想了解更多关于 Gensim 中 Word2Vec 的知识,这里有一个由 Gensim 的创建者 Radim Rehurek 编写的教程。
好吧,这是个不错的热身。在下一节中,我们将创建一个嵌入我们自己的 Word2Vec。
🔨手动 Word2Vec —方法 1
我们将从语料库中查找词汇开始。我们将为词汇表中的每个标记赋值:
vocabulary = sorted([*set([token for document in corpus for token in
document])])
n_vocabulary = len(vocabulary)
token_index ={token: i for i, token in enumerate(vocabulary)}
token_index

作者图片
现在,我们将制作标记对作为神经网络的准备。

作者图片
token_pairs = []for document in corpus:
for i, token in enumerate(document):
for j in range(i-window, i+window+1):
if (j>=0) and (j!=i) and (j<len(document)):
token_pairs.append([token] + [document[j]])n_token_pairs = len(token_pairs)
print(f"{n_token_pairs} token pairs")token_pairs[:5]

作者图片
令牌对已经准备好了,但是它们仍然是文本形式。现在我们需要对它们进行一次热编码,以便它们适用于神经网络。

作者图片
X = np.zeros((n_token_pairs, n_vocabulary))
Y = np.zeros((n_token_pairs, n_vocabulary))for i, (focus_token, context_token) in enumerate(token_pairs):
X[i, token_index[focus_token]] = 1
Y[i, token_index[context_token]] = 1
print(X[:5])

作者图片
现在输入数据已经准备好了,我们可以建立一个只有一个隐藏层的神经网络:
tf.random.set_seed(42)
word2vec1 = Sequential([
Dense(units=dimension, input_shape=(n_vocabulary,),
use_bias=False, name='embedding'),
Dense(units=n_vocabulary, activation='softmax', name='output')
])
word2vec1.compile(loss='categorical_crossentropy', optimizer='adam')
word2vec1.fit(x=X, y=Y, epochs=100)

作者图片
我们指定隐藏层没有偏见条款。因为我们希望隐藏层有线性激活,我们不需要指定。图层中的单元数反映了矢量的大小:dimension。
让我们从隐藏层中提取权重,我们的嵌入。
embedding1 = pd.DataFrame(columns=['d0', 'd1'])
for token in token_index.keys():
ind = token_index[token]
embedding1.loc[token] = word2vec1.get_weights()[0][ind]
embedding1

作者图片
使用我们的新嵌入,让我们看看与'king'最相似的 3 个单词:
vector1 = embedding1.loc['king']
similarities = {}for token, vector in embedding1.iterrows():
theta_sum = np.dot(vector1, vector)
theta_den = np.linalg.norm(vector1) * np.linalg.norm(vector)
similarities[token] = theta_sum / theta_densimilar_tokens = sorted(similarities.items(), key=lambda x: x[1],
reverse=True)
similar_tokens[1:n+1]

作者图片
太好了,这说得通。我们可以保存嵌入并使用 Gensim 加载它们。一旦加载到 Gensim,我们可以检查我们的相似性计算。
with open('embedding1.txt' ,'w') as text_file:
text_file.write(f'{n_vocabulary} {dimension}\n')
for token, vector in embedding1.iterrows():
text_file.write(f"{token} {' '.join(map(str, vector))}\n")
text_file.close()embedding1_loaded = KeyedVectors.load_word2vec_format('embedding1.txt', binary=False)
embedding1_loaded.most_similar(positive=['king'], topn=n)

作者图片
Gensim 计算的相似性与我们的手动计算相匹配。
我们现在将可视化向量空间中的嵌入:
sns.lmplot(data=embedding1, x='d0', y='d1', fit_reg=False, aspect=2)
for token, vector in embedding1.iterrows():
plt.gca().text(vector['d0']+.02, vector['d1']+.03, str(token),
size=14)
plt.tight_layout()

作者图片
在下一节中,我们将利用面向对象的编程方法手动创建单词嵌入。
🔨手动 Word2Vec —方法 2
我们将从创建一个名为Data的类开始,它集中了与数据相关的任务:

作者图片
我们可以看到,corpus属性看起来和前面几节中的一样。
len([token for document in data.corpus for token in document])

作者图片
我们的玩具语料库中有 32 个代币。
len(data.focus_context_data)

作者图片
与之前不同的是,data.focus_context_data没有被格式化为标记对。相反,这 32 个标记中的每一个都与它们所有的上下文标记映射在一起。

作者图片
np.sum([len(context_tokens) for _, context_tokens in
data.focus_context_data])

作者图片
像以前一样,我们总共还有 56 个上下文标记。现在,让我们将关于 Word2Vec 的代码集中在一个对象中:

作者图片|仅部分输出
我们刚刚训练了我们的自定义 Word2Vec 对象。让我们检查一个样本向量:
word2vec2.extract_vector('king')

作者图片
我们现在来看看与'king'最相似的三个词:
word2vec2.find_similar_words("king")

作者图片
这很好。是时候将嵌入转换成数据帧了:
embedding2 = pd.DataFrame(word2vec2.w1, columns=['d0', 'd1'])
embedding2.index = embedding2.index.map(word2vec2.data.index_token)
embedding2

作者图片
我们现在可以很容易地看到新的嵌入:
sns.lmplot(data=embedding2, x='d0', y='d1', fit_reg=False, aspect=2)
for token, vector in embedding2.iterrows():
plt.gca().text(vector['d0']+.02, vector['d1']+.03, str(token),
size=14)
plt.tight_layout()

作者图片
正如我们之前所做的那样,我们可以再次保存嵌入并使用 Gensim 加载它,然后进行检查:
with open('embedding2.txt' ,'w') as text_file:
text_file.write(f'{n_vocabulary} {dimension}\n')
for token, vector in embedding2.iterrows():
text_file.write(f"{token} {' '.join(map(str, vector))}\n")
text_file.close()embedding2_loaded = KeyedVectors.load_word2vec_format('embedding2.txt', binary=False)
embedding2_loaded.most_similar(positive=['king'], topn=n)

作者图片
在计算余弦相似度寻找相似词时,我们这次用了scipy。除了浮点精度误差,这种方法与 Gensim 的结果相匹配。
这就是这篇文章的全部内容!希望您已经对什么是单词嵌入以及 Word2Vec 如何使用 Skip-gram 算法生成单词嵌入有了基本的了解。到目前为止,我们关注的是用于 NLP 的 Word2Vec,但是这种技术对于推荐系统也是有帮助的。这里有一篇关于这方面的深刻文章。如果你想了解更多关于 Word2Vec 的知识,这里有一些有用的资源:
◼️ 第二讲|单词向量表示法:word 2 vec—YouTube
◼️Google code archive—Google code project hosting 的长期存储
◼️ word2vec 参数学习讲解

由 Milad Fakurian 在 Unsplash 上拍摄的照片
您想要访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果您使用 我的推荐链接成为会员,您的一部分会费将直接用于支持我。
谢谢你看我的帖子。如果你感兴趣,这里有我的一些帖子的链接:
◼️️ 管道、ColumnTransformer 和 FeatureUnion 解释◼️️feature union、ColumnTransformer &管道用于预处理文本数据t5】◼️用这些提示丰富您的 Jupyter 笔记本
◼️ 用这些提示组织您的 Jupyter 笔记本
◼️ 解释 Scikit-用 SHAP 学习模型
◼️️ 在 scikit 中选择特性
◼️️ 比较
再见🏃 💨
目标检测中非极大值抑制算法的直观性与实现
本文解释了非最大值抑制技术的使用和实现,该技术用于图像中的对象检测

描绘非最大抑制的封面艺术。(来源:图片由作者提供)
计算机视觉是能够识别和理解图像和场景的人工智能的基本领域之一。它包括各种子领域,例如图像识别、对象检测、图像分割、图像生成、图像超分辨率等等。由于大量的实际使用案例,对象检测可能是计算机视觉最深刻的方面。
目标检测
对象检测针对计算机系统在图像中定位对象并识别每个对象的能力。对象检测已经广泛用于人脸检测、车辆检测、行人计数、安全系统和自动驾驶汽车。“对象检测模型从向端到端学习范式的发展中受益匪浅:建议、特征和分类器成为一个神经网络,将一般对象检测的结果增强了两倍1。”通常,所有现代对象检测模型都遵循三步过程:
- 建议窗口的搜索空间(通过滑动窗口或使用建议稀疏)。
- 用分类器/回归器对窗口进行评分。
- 组合可能属于同一对象的窗口。
这最后一步被称为 “非最大抑制”
边界框
在物体检测中,我们通常使用包围盒来描述物体在图像中的空间位置。边界框是一个矩形,使用左上和右下坐标绘制。另一种常用的边界框表示包含矩形的中心以及矩形的高度和宽度。
非最大值算法(NMS)
可以使用下面的步骤来解释该算法:
输入:边界框列表以及类名和每个检测到的对象的输出概率。
- 移除输出概率分数小于指定阈值的边界框。
- 按照输出概率的降序排列剩余边界框的列表。
- 遍历边界框的排序列表,直到至少剩下一个元素。
- 从列表中移除第一个边界框,并将其标记为“当前元素”此外,检查其与同一类对象列表中其他元素的边界框的并集交集(IOU)。如果 IOU 高于指定阈值,则从列表中删除该元素,并将当前元素添加到最终列表中
- 重复步骤 3 和 4。
- 返回'最终列表'
NMS 算法预演
假设,我们有下面的图像(图 1)包含两只狗(左一只:玛雅和右一只:佐罗),并且我们有一个对象检测模型来区分图像中的玛雅和佐罗。

图一。两只狗坐着:玛雅(左)和佐罗(右)。(来源:图片由作者提供)
当使用上面的图像对我们的对象检测模型(没有 NMS)进行推理时,我们将得到如图 2 所示的输出。在这里我们可以看到,我们得到了多个边界框,在单个对象上有各自的概率分数。

图二。具有相应输出概率的对象上的多个边界框。(来源:图片由作者提供)
我们的目标是为一个对象选择最合适的边界框。换句话说,我们必须从概率为 0.94、0.68 和 0.47 的三个框中为玛雅选择一个边界框。同样,我们也必须从概率为 0.9 和 0.58 的两个边界框中为佐罗找到最佳边界框。
根据该算法,我们将首先丢弃所有那些概率分数小于指定阈值的边界框。例如,如果我们将阈值设为 0.5,我们将丢弃 Maya 概率为 0.47 的边界框。此外,我们将找到具有最高概率分数的包围盒,并将其 IOU 与同类的所有其他包围盒进行检查。如果 IOU 高于阈值(表示相同的对象),我们丢弃具有较小概率分数的边界框。类似地,我们将对图像中所有检测到的对象执行这些步骤。最终输出如图 3 所示。

图 3。应用非最大抑制后的最终边界框。(来源:图片由作者提供)
密码
首先,我们将初始化概率置信阈值和 IOU 阈值。例如,如果边界框的概率低于概率置信度阈值,那么我们将丢弃该边界框。类似地,如果 IOU 分数高于定义的阈值,我们将不考虑具有低输出概率的边界框。
import cv2
class NMS:
def __init__(self) -> None:
self.conf = 0.5
self.iou_threshsold = 0.4
下面代码中的 IOU 函数计算两个区域的 IOU。顾名思义,IOU 就是两个区域的交集面积和两个面积的并集的比值。在 IOU 函数中,bboxes1 和 bboxes2 是一个包含四个元素的列表,它们是:
[ **X**(top-left), **Y**(top-left), **X**(bottom-right), **Y**(bottom-right) ]
下面的代码在图像上绘制边界框,并将概率分数放在框的顶部。参数 'images' 是一个图像对象, 'bboxes_list' 包含被检测对象的坐标、类别和概率输出。
bboxes_list = [**class**, **X**(top-left), **Y**(top-left), **X**(bottom-right), **Y**(bottom-right), **output_probability**]**Sample values:**
0 187 90 586 607 0.94
0 120 116 600 370 0.68
1 511 185 961 418 0.58
0 340 145 568 478 0.47
1 524 70 920 565 0.92
下面的函数是上述 NMS 算法的实现。该函数在应用非最大值抑制算法后返回所需的边界框。
以下是 NMS 类的驱动程序代码。我们首先读取‘coordinates . txt’来获取包围盒的坐标和其他细节;然后,我们应用 NMS 算法得到想要的包围盒。
**coordinates.txt**
0 187 90 586 607 0.94
0 120 116 600 370 0.68
1 511 185 961 418 0.58
0 340 145 568 478 0.47
1 524 70 920 565 0.92
完整代码可从以下网址获得:
https://github.com/prateekchhikara/non-max-suppression
结论
本文概述了非最大值抑制算法的需求以及 python 实现。除此之外,我们用一个图像例子解释了这个算法。
参考
- Hosang,Jan,Rodrigo Benenson 和 Bernt Schiele。“学习非最大抑制。”IEEE 计算机视觉和模式识别会议论文集。2017.
高维空间中的直觉
原文:https://towardsdatascience.com/intuitions-in-high-dimensional-spaces-c22f0441ce19
高维空间的反直观几何性质
我们对距离、面积和体积的许多直觉在高维空间中会失效。在 AI/ML 和数据科学中,我们处理的是高维特征数据;重要的是要理解我们在二维和三维空间中的日常经历是如何没能让我们为更高维空间做好准备的!
为什么重要:我们对距离、体积和点数分布的假设并不总是正确的!当处理高维数据时,我们现有的直觉会将我们引入歧途。
我第一次遇到这些想法是在“关于高维空间中距离度量的惊人行为”1。看看吧!

由 Andrew Kliatskyi 在 Unsplash 上拍摄
距离
通常,我们想要测量一个样本离它的邻居有多远,一个质心,或者一些“正常”的度量通常,我们将距离量化为一个范数。

Lp 定额定义。等式来自维基百科,文本在CC Attribution-share like License下可用。
因为 p 等于 1,这恢复了 L1 范数。因为 p 等于 2,这恢复了 L2 范数。
计算两个 n 维点之间的距离。
现在,我们随机生成一些 n 维正态数据。
采样 100 个同内径高斯 n 维点。
为了识别异常值,我们可能希望找到离某个已知分布中心更远的点。因为我们使用零中心高斯模拟了这些点,所以我们可以计算到原点的距离。
到原点的最远距离和最近距离的比值。
令人惊讶的是,当我们增加维度时,我们遇到了一些奇怪的事情:离原点最远的点与离原点最近的点的距离几乎相同!我们可以将此形式化为最远样本到原点的距离除以最近样本到原点的距离的比值。
作为维数函数的 Lp 范数比。
我们常用的两种距离度量——L1和L2——在高维空间中变得信息量更少了!对于我们的二维数据,离原点最远的点比离原点最近的点远将近 75 倍。然而,在我们的 100 维数据中,这个比率大约是 1.1。
面积/体积
所以距离与我们的期望不符。让我们来看看另一个同样基本的属性:面积/体积。我们来考虑一下圆的 n 维类比和正方形的 n 维类比的区别!
我们可以在一个二维正方形中内接一个二维圆,我们知道它们的面积比:πr 对(2r)其中 r 是圆的半径和正方形边长的一半。如果我们估计 r 的比值为 1,我们就能把π恢复到 4。我们可以在三维空间中做同样的事情,球体的体积为-(4πr)/3,立方体的体积为-(2r)!同样,我们可以检查 1,4π/3 与 8 的比值。我们的正方形的面积大约是我们的圆的面积的 1.27 倍。我们的立方体的体积大约是球体面积的 1.91 倍。
我们可以继续向更高维度发展!圆形进步到球形进步到 n 维 n 球/超球。正方形进展到立方体进展到 n 维超立方体。这些更高维度几何的体积复制如下!
超球和超立方体体积的定义。
现在让我们假设半径和半边长为 1,再来看看比率。我们将增加维度,并绘制 n 球的体积除以超立方体的体积。
计算体积比作为维度的函数。

超球的体积除以超立方体的体积的极限,当我们缩放维度接近零时!
难以置信!在高维空间中,球体模拟占据的立方体模拟越来越少。立方体中几乎所有的体积都在角上!当沿大量维度对特征数据进行切片时,这是一个重要的考虑因素。
关键要点
这些观察提出了一个重要的实际挑战。在高维数据中,Lp 范数不是相似性的有效度量。随着维度的增加,我们对 2D 和 3D 空间的直觉不再成立,即使是我们熟悉的高斯分布也是如此。当采样或过滤数据时,我们需要注意我们空间的几何形状!
参考
1阿格沃尔 CC,欣内堡 A,凯米达。高维空间中距离度量的惊人行为。数据库理论国际会议,2001 年 1 月 4 日(第 420-434 页)。斯普林格,柏林,海德堡。
感谢阅读!如果你有更正,问题,或者其他你想看到我解释的内容,请在评论中告诉我。
人工神经网络直观指南
原文:https://towardsdatascience.com/intuitive-guide-to-artificial-neural-networks-17805150e91a
人工神经网络功能简介

Preethi Viswanathan 在 Unsplash 上拍摄的照片
人工神经网络(ANN)是人工智能和机器学习领域最常用的术语。在这篇文章中,我们想更仔细地看看简单网络的构造,并希望消除许多人的恐惧。
我们将尝试在本介绍中尽可能少地使用数学,以便即使是不熟悉机器学习主题的读者也能容易地接触到该主题。
人工神经网络基于人脑的生物结构。这用于建模和解决困难的基于计算机的问题和数学计算。
构建模块:神经元
在我们的大脑中,从感觉器官接收到的信息被记录在所谓的神经元中。这些细胞处理信息,然后传递输出,导致身体的反应。信息处理不仅发生在单个神经元中,也发生在多层节点网络中。
在人工神经网络中,这种生物学原理被复制并以数学方式表达。神经元(也称为节点或单元)处理一个或多个输入,并从中计算出一个输出。该过程执行三个步骤:

权重因子决定了输入对于神经元解决问题的重要性。如果输入非常重要,系数 w 的值会变大。不重要的输入值为 0。
2.对神经元的所有加权输入求和。此外,增加了偏置 b:

3.随后,结果被赋予所谓的激活函数。

有多种激活功能可供使用。在许多情况下,这是乙状结肠功能。这将取值并将它们映射到 0 和 1 之间的范围内:

Sigmoid 函数|来源:作者图片
这对神经网络有好处,即来自步骤 2 的所有值都在给定的较小范围内。因此,sigmoid 函数限制理论上位于(- ∞,+ ∞)之间的值,并将它们映射到(0,1)之间的范围内。
既然我们了解了单个神经元的功能以及节点内的各个步骤,我们就可以转向人工神经网络了。这只是这些神经元在不同层次上的集合。
网络:输入层、隐藏层和输出层
信息通过网络的不同层传递:
- 输入层:在传递到下一层之前,模型的输入在神经元中被输入和处理。
- 隐藏层:一个或多个所谓的隐藏层接管实际的信息处理。来自前几层的输入在神经元中以加权的方式进行处理,并传递到下一层。这将一直持续到到达输出层。由于这一层中的计算是不可见的,而是在“隐藏”中进行的,这些神经元的集合被称为隐藏层。
- 输出层:该层跟随最后一个隐藏层,接受神经元的输出。这一层中节点的输出包含神经网络的最终结果或决策。

神经网络|来源:作者图片
人工神经网络是如何学习的?
在人工智能的背景下,人们经常谈论这样一个事实,即模型必须经过训练,并且需要大量数据才能提供良好的结果。但是这个过程对于人工神经网络到底意味着什么呢?
从数据中,为每个单独的数据集计算遍历网络时产生的结果,并进行比较,以查看网络的结果与数据集的实际结果相比有多好。在这个过程中,人工神经网络的预测应该越来越接近实际结果。
为此,人工神经网络有一个调节螺丝,通过每个训练步骤使结果更接近实际结果,即单个神经元输出的加权。在学习过程中,他们的权重不断变化,以改变结果的准确性。也就是说,每个神经元决定前面神经元的哪些输出对它们的计算重要,哪些不重要。在最好的情况下,这个权重随着每个新的数据集而增强,并且总体结果变得更加准确。
这里举一个小例子来说明这一点,当然不建议模仿。数学课上,有三个好朋友坐在你旁边。对于课堂上计算的每一道题,你可以让他们三个都告诉你他们的结果,因为你自己不能再进一步了。他们三个总是愿意给你一个数字作为解决方案。因此,在课程(训练阶段)中,你要找出三个同学中哪一个通常成绩最好。
所以对于你计算的每个任务,你问他们三个的结果,通过与老师的结果比较,找出你可以从三个同学中的哪一个得到最好的结果。根据子领域的不同,你会发现一个输入比另一个更好。因此,您可以在培训阶段更改和优化权重。在考试中,你将确切地知道为了得到正确的结果,你必须求助于三者中的哪一个。
这正是网络中所有神经元所做的。在训练期间,根据有多少神经元“坐在”附近,给它们一定数量的输入。在训练过程中,他们在每一步都决定哪一个初步结果最适合他们,然后将它与实际结果进行比较,看他们是否正确。在训练之后,也就是在测试中,他们就能准确地知道哪些先前的神经元是最重要的。
损失函数
人工神经网络的目标是在每个训练步骤中减少自我预测结果和实际结果之间的差异。为了实现这个目标,并且能够跟踪到那里,就有了所谓的损失函数。它提供了一个数学陈述,说明网络的响应与期望的实际响应有多远。
在最佳情况下,损失函数值为 0,因为人工神经网络的结果与实际结果完全匹配。因此,在每个训练步骤中,尝试将损失函数近似为值 0。为了找到损失函数的最小值并快速逼近它,例如使用梯度下降。
这是你应该带走的东西
- 人工神经网络由大量神经元组成。在训练阶段,单个神经元的权重被改变和优化。
- 网络由三种不同的层类型组成:输入层、隐藏层和输出层。
- 人工神经网络的目标是最小化损失函数,即预测结果和实际结果之间的差异。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你想让无限制地访问我的文章和数以千计的精彩文章,请不要犹豫,通过点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5获得会员资格*
** **
生成对抗网络的直观介绍
解释甘斯在计算机视觉中的魔力

生成器-鉴别器直觉(图片由作者提供)
假设一个艺术伪造者试图创作达芬奇的假画,一个侦探试图鉴别这幅画是真迹还是赝品。
起初,伪造者可能不擅长创作达芬奇画作的复制品,侦探可能更容易发现假画。
随着时间的推移,伪造者了解到侦探在画作中检查的关键成分,以将其分类为真画或假画。类似地,随着侦探看到更多的真画和伪画,侦探能够了解区分这两者所需的更精细的细节。
最终,伪造者会很好地学会这个技巧,以至于伪造者会开始愚弄侦探,让他相信这些假画是达芬奇的真迹。现在我们可以说对伪造者的训练已经完成了。
这正是 GAN 的工作原理。
GANs 简介
生成性对抗网络(GANs)是近十年来令人惊叹的创新之一,它导致了最近许多最先进的产品。2014 年,Ian Goodfellow 等人在论文中首次提出了 GAN。自问世以来,GANs 已有多种不同的版本,以满足不同的需求。
生成对抗网络(GANs)是一种基于深度学习的生成模型,它发现输入数据中的潜在模式,并从中生成新的样本。不要被名字或定义吓倒,一旦你读到这篇文章的结尾,你会被它的简单性所折服。
基本的 GAN 架构有两个神经网络,生成器和鉴别器。生成器是生成假数据的伪造者,鉴别器是检测器,其主要作用是将输入数据分类为真或假。一旦生成器能够创建可以欺骗鉴别器认为它是真实的数据,那么模型训练就完成了,并且生成器被训练来创建“好的”假数据。
深度卷积生成对抗网络或 DCGANs 是专门用于图像数据的 GAN 的流行方法之一。DCGAN 在发生器中的输入噪声向量和输出图像之间添加卷积层。此外,鉴别器使用卷积层将实际和生成的图像分类为真实或伪造。
甘——监督学习还是非监督学习?
GAN 内部使用两个神经网络,生成器和鉴别器。
生成器网络从潜在向量(在 DCGAN 的情况下是噪声向量)生成图像,并且是一种无监督的机器学习。鉴别器网络将图像分类为真或假,并且是一种监督学习,因为标签(真/假)用于训练模型。
现在转到 GAN 的工作,在训练期间,GAN 接受两个输入,随机噪声数据和未标记的输入数据。使用这两个输入,它生成类似于输入数据的数据。因为 GAN 的所有输入都是未标记的,所以 GAN 是一种无监督的机器学习。
甘模特培训
GAN 内部有两个相互竞争的神经网络。发生器网络的目标是欺骗鉴别器网络,而鉴别器网络的目标是正确识别输入是真还是假。
培训发电机
在我们的伪造者-侦探故事中,这是伪造者每次迭代学习的方式:

发电机从错误中吸取教训(图片由作者提供)
发生器架构是一个神经网络,它接收噪声输入向量并将其转换为输出图像(在 DCGAN 的情况下)。
下面是从 DCGAN 论文中挑选的一个发电机架构示例。如下图所示,发生器的输入是一个大小为 100 的噪声矢量 z。这首先被投影和整形为 1024×4×4 数据,随后是 512×8×8、256×16×16 和 128×32×32 的卷积层。最后,将其输入输出层,生成 64x64 的 RGB 图像。

DCGAN 发生器示例(来源: DCGAN paper
训练鉴别器
下面是侦探如何在每次迭代中学习:

鉴别器从先前的预测中学习(图片由作者提供)
鉴别器是简单的二元分类器,将输入数据分类为真实或虚假。在 DCGAN 的情况下,鉴别器是一个卷积神经网络(CNN ),它进行二进制分类。CNN 的输入是一幅图像,鉴别器需要将其分类为真实图像或虚假图像。
甘的失落——直觉
如前所述,GAN 架构中涉及两个神经网络,因此在模型训练步骤中需要反向传播两种类型的损耗。
鉴频器损耗
鉴别器对来自生成器的虚假数据和真实数据进行分类。

鉴频器丢失(图片由作者提供)
当它将发电机数据分类为假数据或者将实际数据分类为真数据时,那么它做得很好,否则它会对输出进行错误分类,并且该模型需要针对该输入进行重新训练。
因此,当鉴别器将假图像分类为真实图像或者将真实图像分类为假图像时,它被罚以鉴别器损失,并且该损失通过鉴别器网络反向传播以更新权重和偏差。
请注意,在训练鉴别器时,发生器权重和偏差将被冻结。
发电机损耗
生成器的作用是从噪声输入中创建图像,并且它没有任何关于预期输出类型的标记信息。因此,发生器使用鉴别器的输出来更新其网络参数。

发电机损耗(图片由作者提供)
如果鉴别器将生成器生成的图像误分类为真实图像,则生成器正在完成其工作,但是如果鉴别器可以识别出生成的图像是赝品,则生成器需要更新。
因此,当鉴别器识别出假图像并将其分类为假图像时,则计算发电机损耗并将其反向传播到发电机网络以更新其权重和偏差。
请注意,在训练发生器时,鉴别器权重和偏差将被冻结。
要对甘斯背后的数学有一个基本的了解,可以参考这篇后续文章: 解码甘斯 中的基本数学
甘斯有什么酷的?
这里有一个简单的例子。
图像到图像的翻译——不成对方法
这里,从一个域到另一个域进行图像到图像的转换,而不需要输入图像和输出图像之间有任何关系。有不同类型的 GAN 实施这种方法,如 CycleGAN、DualGAN 和 DiscoGAN。
示例:
- 从马域转换到斑马域
- 将夏季图像更改为冬季图像

来源:周期一致的敌对网络
图像到图像翻译—成对方法:
这里,在输入和输出之间存在自然的映射,或者对于每个输入图像存在映射的输出图像。条件敌对网络使用这种方法。
示例:
- 将街道视图地图更改为卫星视图地图
- 黑白到彩色图像
- 草图到实际图像

来源:条件敌对网络
创造艺术一代
创意对抗网络(CAN)是 GAN 的扩展,其中模型学习艺术家的风格,并创建与该风格匹配的新绘画的图像。在这里,鉴别器在来自画家的大量真实艺术品上接受训练,它可以准确地将图像分类为真/假。此外,鉴别器还对艺术品所属的时间段进行分类。生成器将使用这个额外的时间周期度量以及真/假分类来训练自己。

来源:创意对抗网络
还有更少的…
- 从 2D 图像生成三维模型
- 将文本标题转换为图像
- 从低分辨率图像生成高分辨率图像
- 生成不存在的人的真实图像
- 将照片转换为表情符号
- 从一个领域到另一个领域创建医学图像
结论
这只是展示 GANs 强大功能的一小部分应用示例。随着数以千计的研究论文和 GANs 的几个变种,很明显,这项技术正在积极发展,它在未来拥有巨大的潜力。
当我们到达这篇文章的结尾时,我希望你已经对 GAN 的工作有了一个基本的了解,并且它帮助你更深地潜入这个 maGANical 的世界。祝你好运!!
零售库存管理—定期审查政策
原文:https://towardsdatascience.com/inventory-management-for-retail-periodic-review-policy-4399330ce8b0
根据定期审查政策实施库存管理规则,以减少商店补货数量

(图片由作者提供)
目标
设计库存管理规则,使最小化补货数量并满足商店需求。
介绍
对于大多数零售商来说,库存管理系统采取固定、基于规则的方法来预测和补货订单管理。
目标是建立一个补货政策,最小化订购、持有和短缺成本。
在之前的文章中,我们已经建立了一个基于连续审查库存策略的模拟模型,假设需求的正态分布。
https://www.samirsaci.com/inventory-management-for-retail-stochastic-demand-2/
然而,当你处理一个可能有不同补货周期长度的大型项目组合时,这种政策可能低效。
在本文中,我们将改进这个模型,并用 Python 实现一个定期审查策略,以限制的补充数量。
💌新文章直接免费放入你的收件箱:时事通讯
**SUMMARY**
**I. Scenario** 1\. Problem Statement*As an Inventory Manager of a mid-size retail chain, you are in charge of setting the replenishment quantity in the ERP.*
2\. Limits of the continuous review policy*What is the impact a continuous review policy on the number of replenishments when you handle several hundred SKUs?*
**II. Periodic Review Policy: Order-Up-To-Level (R, S)** 1\. Introduction of the Inventory Policy2\. Definition of the Safety Stock3\. How do you define k?
**III. Example of Replenishment Policies
IV. Conclusion**
一.情景
1.零售库存管理
作为一家中型零售连锁企业的库存经理,你负责在 ERP 中设置补货数量。
因为你的仓库运营经理在抱怨订单频率,你开始挑战 ERP 中实施的补货规则,尤其是针对快速跑者。
之前,我们已经基于持续审查策略实施了几个库存规则。
在每个时间 t 我们检查现有库存(IOH):

方程式—(图片由作者提供)
举一个 SKU 的例子

N(2000,50)的持续审查政策
我们每年需要 27 个补货订单。
问题 如果你管理2294 个 SKU,你需要多长时间订购一次?
2.持续审查政策的限制
你可以在我的 Github 资源库中找到带哑数据的源代码:链接
我的作品集其他供应链相关文章: Samir Saci
我们假设我们只有 365 天的销售额
- 10 家店铺 (STORE_1,… STORE_10)
- 1 产品系列(食品 _ 杂货)
- 2294唯一 SKU(商店 ID +产品系列+商品代码)
模拟 1
让我们对 1 SKU 实施持续审查政策
- SKU:商店 _ 1-食品 _ 杂货店 _009
- k = 3
(需求分布标准差的 3 倍)设计的安全库存 - 订单数量:Q = 3 x 平均数量 _ 年销售量

持续审查政策1 SKU——(图片由作者提供)
评论第 18 个 100 天补货
模拟 2
如果我们现在有 10 个 SKU需要管理,会怎么样?

补充了 10 个 SKU——(图片由作者提供)
评论第 54 期第 100 天补货
模拟 3
如果我们有 100 个 SKU要管理呢?

补充了 100 个 SKU——(图片由作者提供)
备注935补货前 100 天不到 10 天无订单
模拟 4
如果我们有 2,294 个 SKU需要管理,会怎么样?

补充 2994 个 SKU——(图片由作者提供)
评论前 100 天 19267 次补货(不到 10 天没有订单)
用这种方法,当你有一个大的投资组合时,补充的数量会激增。
解决方案
我们将利用定期审查来制定补货政策。
二。定期审查政策:订单逐级(R,S)
1.库存政策介绍
为了解决补充频率的问题,让我们引入一个定期检查策略(s,Q)
- 在每个周期 R,将审查现有库存 (IOH) 水平
- 商店将订购达到 S 的库存水平
为了简化理解,让我们介绍一些符号:

方程式—(图片由作者提供)
2.最高级别订单的定义
如何计算等级 S:

方程式—(图片由作者提供)
计算 S 水平是为了将库存水平设置为满足审查期间和补货提前期的需求。
3.你怎么定义 k?
您的绩效指标将直接受到安全库存水平的影响
- 您为两个指标中的任何一个设定目标(例如:我希望周期服务水平为 95%)
- 根据需求的分布(正态分布、泊松分布)来计算达到这个目标的 k 值
- 你修正你的再订购点
代号
三。补充政策示例
1.以 SKU 为例:商店 _ 1-食品 _ 杂货店 _009
评审周期:R = 10 天
k = 1

经验需求的定期审查政策 SKU =STORE _ 1-FOOD _ 杂货店 _ 009——(图片由作者提供)
备注前 100 天 10 次补货 vs. 18 次补货无缺货
2.如果你处理全部投资组合,需要补充多少资金?

补充 2994 个库存单位(图片由作者提供)
评论13986 次补货vs .19267 次补货前 100 天只有 10 天补货
四。结论
关注我的 medium,了解更多与供应链数据科学相关的见解。
实施定期审查政策后,您将通过以下方式降低运营成本
- 限制补货的时间窗口
这将优化您的库存管理、订单准备和运输管理资源。 - 减少补货数量
这将增加每次补货的数量,并为同时交货的商店提供装运整合的机会。
关于我
让我们连接上 Linkedin 和 Twitter ,我是一名供应链工程师,正在使用数据分析来改善物流运作和降低成本。
如果你对数据分析和供应链感兴趣,可以看看我的网站
1 —供应链科学,华莱士·j·霍普
调查《龙屋》和《权力的游戏》角色之间的家庭关系
使用 Neo4j 交互式分析和可视化家谱
我已经有一段时间没有写博客了。一直在忙着看《龙族之家》节目。然而,现在第二季的等待已经开始,我突然有了一些空闲时间。我看到一些社交帖子描述了《龙屋》和《权力的游戏》中角色之间的联系。由于任何类型的联系都在我的领域内,并且我喜欢写它们,所以我决定写一篇帖子来展示在 Neo4j 生态系统中快速分析和可视化家庭联系是多么容易。
《龙屋》的故事情节发生在《权力的游戏》之前大约 200 年。所以《权力的游戏》中的部分角色是龙之家角色的后代。例如,丹妮莉丝·坦格利安的祖先是韦赛里斯和雷妮拉·坦格利安。有一些特定家谱的可视化,但是我还没有看到任何把它们放在一起的尝试。你可能知道,冰与火宇宙中的皇室家族利用婚姻作为政治工具来传播他们的影响力。因此,许多皇室家族在某种程度上通过婚姻结合在一起。
这篇博文将教你如何交互式地搜索和可视化各种角色之间的关系,不需要任何编码或密码知识。如果你是一名数据分析师或渴望成为一名数据分析师,我将在这篇博客的最后部分向你展示如何从 Python 笔记本中执行图形算法,并评估它们的结果。

从珊莎·史塔克的角度看家谱的一个子集。图片由作者提供。
家谱由人和他们的关系组成。在图论中,树是一种特殊类型的图。假设数据集最好用图模型来表示,那么使用图数据库来存储和分析信息是有意义的。如果你读过我的帖子,你会知道我喜欢在我的例子中使用 Neo4j,因为它提供了开箱即用的可视化和算法支持,而无需在你的计算机上安装任何东西。这使得像您这样的读者更容易理解这些示例,因为您不必在本地计算机或。然而,如果你想在你的分析中使用任何其他工具,你是非常受欢迎的。
Neo4j 沙盒
如果您想跟随代码示例,您需要设置一个 Neo4j 数据库。建议你在 Neo4j 沙盒上使用一个空白项目。沙盒选项附带预装的 APOC 和图表数据科学库,以及 Neo4j Bloom,这是一种可视化工具,允许您以交互方式探索和分析图表。
另一方面,如果你想创建一个本地环境,你可以随时下载并安装 Neo4j 桌面应用。
资料组
我们需要一个数据集,其中包含来自《龙之家》和《权力的游戏》时间轴的角色。幸运的是,我们有一些选项不包括组合不同的数据集。像以前很多次一样,我搜索了所有相关信息的社区维基。我偶然发现了冰与火网页的一个维基,它看起来像是一个很好的候选,包含了我们需要的所有信息。网站上的所有内容都可以在 CC BY-SA 3.0 许可下使用。
像其他 wikis 一样,冰与火页面包含所有角色的列表,以及每个角色的信息框中结构良好的信息。

正如你所看到的,从家庭关系到皇室职责和忠诚,有很多关于角色的信息。我制作了一个 CSV 文件,以便更容易地构建一个图表,而不必每次都去浏览网站。在导入数据时,我注意到需要几个数据清理步骤,所以我发布了一个 Jupyter 笔记本,专门用于将数据集导入和清理到 Neo4j 中。
https://github.com/tomasonjo/blogs/blob/master/ice%26fire/Ice%26Fire_import.ipynb
您只需要更改 Neo4j 凭证,然后就可以运行所有的单元格来生成下面的图形模型。

图形模型。图片由作者提供。
字符位于我们图形模型的中心。我们知道他们的家庭关系,这些关系被标记为父亲、母亲和配偶关系。我们也知道他们属于哪种文化,以及他们对不同派别的忠诚。最后,我还输入了角色出现在哪些书籍或电视节目中。
冰与火的宇宙是巨大的,因为我们在图表中有 3653 和 563 个派别。冰与火的维基提供了更多关于派系、文化、地点和其他实体的信息,我跳过了这些,因为这次分析的重点是家庭关系。
交互式可视化
我们将从通过 Neo4j Bloom 中的网络可视化交互分析角色之间的关系开始。Neo4j Bloom 允许没有编码或密码知识的用户有效地探索和分析任何图形数据集,这意味着你可以用它来打动你的朋友和家人,并帮助他们以令人信服的方式探索冰与火的世界。
您可以通过单击下拉箭头并选择 Neo4j Bloom 选项,在 Sandbox 中打开 Neo4j Bloom。

如何在沙盒环境下打开 Bloom?图片由作者提供。
接下来,您需要点击屏幕右侧的创建新的按钮,并选择生成透视图,这将自动推断图形模式。

自动生成 Bloom 透视图。图片由作者提供。
现在你已经准备好了。Bloom 提供了接近自然的语言搜索,您可以使用它来探索图表。

Neo4j Bloom 中的近自然搜索。作者视频。
Neo4j Bloom 提供了一个搜索栏,可以用来查找相关节点并检查它们的关系和属性。我们还能够在搜索栏中定义更复杂的图形模式,这超出了本文的范围。你可以在由乔纳森·登撰写的文章之后的中查看一些更新的功能。
我喜欢在 Neo4j Bloom 中定义自定义搜索短语,可以帮助你更快地找到相关的图形模式。不用在搜索栏中描述整个图形模式,您可以使用 Cypher 语句简单地定义它。Cypher 语句还支持可选参数。
例如,假设我们想要定义一个自定义的搜索短语来帮助我们找到任意字符对之间的联系。在实践中,我们将试图找到两个角色之间的最短路径,同时只考虑家谱的关系。
下面的 Cypher 语句查找两个角色之间的最短路径,并限制该路径只能遍历父亲、母亲或配偶关系。
MATCH (s:Character {name:$person1}), (t:Character {name:$person2})
MATCH p=shortestPath((s)-[:FATHER|MOTHER|SPOUSE*]-(t))
RETURN p
Cypher 语句中的参数以美元符号($)为前缀。您可以观察到,我们没有为角色名称硬编码任何值,因为我们想要创建一个工具来帮助用户找到任何一对角色之间的联系。
要定义带参数的搜索短语,我们需要在搜索短语和 Cypher 语句中包含参数。在这两种情况下,参数都以美元符号为前缀。此外,搜索短语中的参数名必须与 Cypher 查询中的参数名相匹配。

在 Neo4j Bloom 中定义自定义搜索短语。图片由作者提供。
每当我们在搜索栏中键入“查找连接自”时,搜索短语将被激活。Neo4j Bloom 的一个很好的特性是它提供了开箱即用的自动补全参数。要启用自动完成,您必须在搜索短语定义的下半部分描述 Bloom 应该提供哪些建议。

布隆自动完成建议定义。图片由作者提供。
从图中可以看出,我们需要定义自动完成建议应该使用 Label-key 特性并设置标签值字符和属性值名称来提供。本质上,建议将通过使用字符节点的 name 属性来提供。
现在我们可以继续测试我们刚刚定义的自定义搜索短语。在这个例子中,我想找到《权力的游戏》系列中的主要人物之一提利昂·兰尼斯特和出现在《龙之家》故事情节中的韦赛里斯一世之间的最短路径。

提利昂·兰尼斯特和韦赛里斯之间的最短路径。
有趣的是,从提利昂到韦赛里斯的连接是从穿越到瑟曦·兰尼斯特和劳勃·拜拉席恩一世开始的。我从来不知道劳勃的祖母是瑞艾尔·坦格利安,在这个例子中,她是坦格利安家族的切入点。坦格利安的名字对我来说很困惑,所以我甚至不想去破译剩下的通往韦赛里斯一号的路。
如果你还记得的话,劳勃·拜拉席恩从疯狂的伊里斯·坦格利安国王那里篡夺了王位。由于劳勃·拜拉席恩至少有坦格利安的四分之一,我想知道他们的关系有多密切。幸运的是,我们可以简单地改变搜索短语中的名称并检查最短路径。

罗伯特·巴拉森和伊里斯·坦格利安之间的最短路径。图片由作者提供。
贝萨·布莱克伍德似乎是劳勃·拜拉席恩和疯王艾瑞斯二世最近的共同祖先。它们的关系比人们想象的更密切。在冰与火的宇宙中探索联系是很容易的。因此,我想你会想要探索更多的关系。
接下来,我们将准备另一个搜索短语,它将一个特定人的所有已知祖先可视化。定义这个图形模式的 Cypher 语句如下。
MATCH p=(c:Character {name:$person})-[:FATHER|MOTHER*]->()
RETURN p
我们只需要将这个 Cypher 语句和搜索短语定义一起输入 Neo4j Bloom。

定义 Neo4j Bloom 中的祖先搜索短语。图片由作者提供。
一旦定义了搜索短语,我们就可以在搜索栏中使用它。在这个例子中,我们可以想象一个人的所有祖先。我决定研究玛格丽·提利尔的祖先。

玛格丽·提利尔的家谱。图片由作者提供。
关于玛格丽·提利尔的祖先,我们知道的并不多。然而,你可能对海托华家族的名字很熟悉。据说玛格丽·提利尔是龙之家的奥托和艾丽森·海塔尔的远亲,尽管没有明确的联系。
图形数据科学
也许你是一名数据科学家,或者你渴望成为一名数据科学家。图表分析和数据科学提供了各种各样的算法,可以增强您的分析工具箱,并帮助您找到对高度关联的数据集的有意义的见解。在这一节中,我将展示如何轻松地将图形算法集成到您的分析工作流中。Neo4j 为 Neo4j Graph Data Science library 提供了一个 Python 客户端,可以让你只使用 Python 代码无缝地执行图形算法。
在这个例子中,我们将在家庭关系网络上执行弱连通分量算法。弱连接组件算法(WCC)用于在给定网络中寻找不同的岛或节点组件。当您忽略关系方向时,一个节点可以到达同一组件中的所有其他节点。

样本图中的可视化弱连通分量。图片由作者提供。
托马斯、艾米和迈克尔形成弱连接部分,艾丽西娅和约翰形成另一部分。例如,Alicia 和 Michael 不在同一个组件中,因为两者之间不存在路径。
在家庭网络的上下文中,WCC 算法可以帮助您检测在某个时间点哪些家庭通过婚姻或孩子结合在一起。此外,WCC 可能会帮助我们评估是否有更大的系列组件在争夺权力。举个例子,如果兰尼斯特和史塔克联姻,而坦格利安和巴拉瑟昂组成另一个阵营,那么我们可以假设这两个阵营正在争夺七大王国的控制权。请注意,这只是一个虚构的例子,与实际的冰与火世界没有任何关系。
我已经准备了一个 Jupyter 笔记本,其中包含了 WCC 示例的所有代码。
https://github.com/tomasonjo/blogs/blob/master/ice%26fire/Ice%26Fire_analysis.ipynb
首先,我们需要为我们的用例设计一个包含相关节点和关系的内存图。例如,因为我们想关注家庭关系,我们将包括角色节点和母亲、父亲以及配偶关系。
G, res **=** gds**.**graph**.**project("family", "Character", ["MOTHER", "FATHER", "SPOUSE"])
为了在家庭网络投影上执行弱连通分量算法,我们简单地运行gds.wcc.stream方法。算法的stream模式以 Pandas 数据帧的形式检索算法结果。
# Execute WCC algorithm
wcc_df **=** gds**.**wcc**.**stream(G)
# Fetch name property from the database
wcc_df["name"] **=** [el["name"] **for** el **in** gds**.**util**.**asNodes(wcc_df["nodeId"]**.**to_list())]
# Derive the last name
wcc_df["last_name"] **=** [
el**.**split(" ")[**-**1] **if** len(el**.**split(" ")) **>** 1 **and** len(el**.**split(" ")[**-**1]) **>** 3 **else** **None**
**for** el **in** wcc_df["name"]
]
结果

我们添加了两行代码,用于从数据库中检索字符的 name 属性并提取姓氏。如果您对 Python 代码有所了解,您会发现姓氏是从姓名中包含多个单词的人提取的,并且姓名中的最后一个单词包含 3 个以上的字符。姓氏提取并不完美,但对于我们的演示来说已经足够好了。
componentId列描述了一个节点所属的社区。如果我们想要计算组件的大小,我们使用 Pandas groupby方法来聚合和计算组件的大小。
wcc_df**.**groupby("componentId")**.**size()**.**sort_values(ascending**=False**)**.**to_frame(
"componentSize"
)**.**reset_index()**.**head()
结果
看起来最大的组件包含 785 个成员。信不信由你,这在现实网络中是很常见的,网络由一个超级组件和几个更小的组件组成。
附:如果你更喜欢 Cypher 聚合而不是 Pandas 聚合,你总是可以用 Cypher 来代替。
可能需要很多不同的家庭加入才能在一个社区中获得总共 785 个成员。此外,请注意,两个皇室家族之间只需要有一次婚姻,并且它们在单个组件中连接在一起。作为分析的最后一部分,我们将检查哪十大家族及其成员中有多少属于最大的组成部分。
largest_component = wcc_df.groupby('componentId').size().sort_values(
ascending=False
).reset_index()['componentId'][0]wcc_df[wcc_df["componentId"] == largest_component].groupby("last_name").size().sort_values(
ascending=False
).to_frame("count").reset_index().head(10)
结果
我会说冰与火宇宙的历史就是皇室联姻和互相残杀。有趣的是这是如何工作的。例如,我们通过他的祖母了解到劳勃·拜拉席恩至少有四分之一的坦格利安血统,但他执意要杀死最后一个真正活着的坦格利安。
由于我们有一些关于人物出生和死亡时间的信息,在家族网络上运行 WCC 算法并评估婚姻如何影响家族的联盟和权力可能是有趣的。
摘要
图表是一种很好的数据模型,用于表示高度关联的数据集。当处理数据点之间的许多显式或隐式关系时,可能值得通过图形数据科学的视角来研究它们。图表数据科学工具箱提供了多种算法,通过分析数据点的连接方式来获得有价值的见解。冰与火宇宙是一个可爱的第一个网络数据集,它可以帮助您学习图形可视化和分析的基础知识,您可以在现实世界的问题中使用这些知识。因此,我鼓励你创建一个沙盒项目并开始你的图表分析之旅。
如前所述,导入和分析笔记本都可以在 GitHub 的上获得。
区分专家数据科学家的无形技能
原文:https://towardsdatascience.com/invisible-skills-that-distinguish-expert-data-scientists-6c3af0dfdbfa
创造高效数据科学家的心智模型和人格框架

笼罩的山峰。作者图片
虽然人们已经用编程做统计有一段时间了,但我们仍然处于数据科学和机器学习工程在工业中的正式作用的早期阶段。到目前为止,我们有一个完善的硬技术技能列表,人们可以通过学习来精通这个领域。这些包括:Python、SQL、数据分析和可视化、特征工程、建模、生产模型等。大多数人还知道其他不容易进入求职者名单的技能:沟通、项目管理等。这些通常被简单地称为“软技能”。
这个想法是,要成为一名专家,你只需要在足够长的时间里,在高水平上执行所有这些硬技能和软技能。我花了很多时间近距离观察和学习高性能的数据科学家,老实说,他们的优秀远远不止这些技能。能够快速编写干净高效的代码或者非凡的沟通能力会让你在这个领域走得很远,但是我注意到了一组模式,它们将最优秀的人与其他人显著区分开来。
这些模式在很大程度上是不可见的,以至于有时甚至这个人自己都不能清楚地说出他们与同龄人的不同之处。随着时间的推移,我看到那些展现出大部分或所有这些技能的人最终获得了惊人的成功。从能够设计有效的机器学习系统,到与商业伙伴建立牢固的关系,让人们相信你的话,体现这些技能的数据科学家没有竞争对手。
将这些概念称为“技能”几乎是一种伤害,因为它们更多的是人格框架,比传统技能需要更长的时间来掌握。列表如下:
- 狐狸和刺猬
- 抗脆弱
- 无法满足的好奇心
- 快速放弃自我
- 讲故事
- 慷慨合作
一个重要的免责声明:我并不是说如果你没有所有这些“隐形技能”你就不合格。显然,对 Python、统计学和通信的高度精通仍然是非常有价值的。我只是说,这些是我见过的最强烈的宗教使用的一套特征,由于它们的结果,卓越的差距变得非常明显。
狐狸和刺猬
“狐狸”通常是一个在较浅的深度快速跨越广泛的概念和领域的人。尽管他们不是任何一个领域的专家,但他们的知识范围很广。他们生活在一个充满可能性的世界里,对细微差别感到很舒服。
相比之下,“刺猬”是深度的化身。他们能够非常好地了解一件事,并且通常对他们要学的东西更加明确。将专业技能与对全局的关注结合起来,他们能够将每个问题简化为一个组织原则。
每种性格类型都有真正闪光的时刻,我见过最好的数据科学家根据团队的需要在两者之间有效地舞蹈。
在项目开始时,当关于数据或策略的信息很少时,最好是做一只老练的狐狸。你应该能够快速遍历大量的信息,在一片噪音中识别信号。如果你在一个狭窄的解决方案中走得太远,你很可能会错过关键的信息,这些信息可能会在以后完全扼杀你的项目。将大量难以理解的信息提炼为具体的信号和噪音需要强大的狐狸能力。
在提取了一定数量的信息后,做一只刺猬通常会更有用。您应该知道问题是什么,什么策略可以缓解和解决问题,为什么这些解决方案会有效,以及如何构建这些解决方案。这需要的深度是大多数狐狸没有的(也不应该有,因为这不是它们的目的)。
平衡广度和深度,同时应对现实的限制,如成本、时间、计算、人员等。—只有专家才能有效完成。
抗脆弱
弹性的演变。弹性是从失败中恢复的能力。抗脆性是由于故障而特别增长的能力。一个有弹性的系统在停机后可以很快恢复在线,但是一个抗脆弱的系统会因为停机而变得更强更好。
这是我最喜欢的作家之一纳西姆·塔勒布在他的书《反脆弱:从无序中获益的事物》中提出的一个概念。一旦了解到它,我就开始在专家数据科学家中大量识别它。当你想到这一点时,它很有意义:在应用机器学习项目上拥有大量工作经验会让你面临大量的失败,同时仍然需要你根据统计理论做出决定。随着时间的推移,这种判断会越来越清晰,直到你能够知道陷阱在哪里,以及哪些陷阱会产生正确的信息来推动前进。
如果你不经常遭遇失败,这是一项很难学习的技能。你需要对可能导致失败的事情做出判断,这样你就可以控制为了知识和成长而愿意承担的失败。再加上必须领导一个项目和团队一起经历这一切,这就变成了一项非常困难但必须掌握的技能。
无法满足的好奇心
在一个信息和能量丰富的世界里,求知欲是你与众不同的地方。由于我们有大量的免费信息,很容易感觉自己是专家,但当你看输出时,它显示了阅读生产质量和生产质量之间的差异。
我们在数据科学领域看到这种情况的一种方式是,人们通过盲目地执行以下 4 个步骤,就能感觉到他们在高水平地工作:
- 导入 sklearn
- GridSearchCV
- 。适合()
- 。预测()
任何步骤本身都没有问题,但是不要好奇为什么某些模型在你的用例中比其他模型表现得更好(在速度、性能等方面)。)真的很不利。任何人都可以阅读一页 sklearn 文档并找出这 4 个步骤,但需要专业知识才能知道即使在使用 GridSearchCV 时仍会过度拟合,如何缓解,为什么应该选择或不选择某些模型,您是否甚至有建模问题或数据问题(提示:这通常是数据问题),等等。
求知欲是专家为自己带来的+1%的进步,随着时间的推移,这转化为难以置信的巨大进步。他们花额外的 5 分钟去读一篇与他们的信念相矛盾的文章,不走捷径去实现他们不理解或无法解释的事情,并且想成为销售者之前的建设者。
这种能力也是“终身学习者”的来源,它不断补充能量,以不断完善一个人的工艺或提高你的能力。这个领域有太多的概念和技术,变化之快是任何一个人都跟不上的,所以要真正涉水,你必须有一种能让你保持高能量水平的特质。
快速放弃自我
这可能是一个微妙的平衡,尤其是当你对一个概念有深刻的了解时,但这是非常必要的,因为通常成功不会归结于理论。我们都见过一个人紧紧抓住自己的知识不放,拒绝接受不同观点的后果。这多半会导致无效的解决方案和关系。
即使科学家们拥有丰富的专家数据知识,我也曾目睹他们试图说服别人。他们不努力成为房间里最聪明的人,他们希望知识尽可能平等、公开地被分享。这是通过有意义的辩论和对真理的追求来实现的。在实践中,这可能意味着他们不愿透露他们所知道的和他们的观点,专家们希望在提供他们的观点之前听到其他人的想法。非常有趣的是,我见过没有经验的从业者比我见过的有经验的从业者更加大声和自豪地宣扬他们的信仰,即使他们的信仰不是真的。
专家意识到这是一个人的手艺,意味着理论和方法只是一个成功项目的一小部分。与你的团队和利益相关者的关系和获得正确的理论一样重要。把你的自我带到讨论中不会与你周围的人建立牢固的关系,即使你是对的,尤其是当你错的时候。
通常,成功也不在于理论,而在于应用。而应用理论可以有很多正确的答案,而理论通常只有一个。无数的正确答案意味着,审查出各种各样的意见,并根据证据和合理的判断做出决定,这是对你有利的。当你以自我为主导时,这是不相容的。
讲故事
正如我在以前的一些故事中提到的,这比良好的数据可视化要重要得多。讲故事是一门艺术,它以清晰简洁的方式传达复杂的思想,因此人们会受到它有意义的影响。它可以整合数据可视化,但主要是影响力,而不是技术能力。
就像交流是如何由语音变化,习惯,肢体语言等组成的。但比其各部分的总和要大得多,影响也是一样的。要施加影响,你首先需要建立一种信任的关系。这在短时间内一点也不容易做到,更不用说你可能要在 30 分钟的会议中完成这一切。
此外,幽默在这里也是一项被低估的技能。幽默在拉近人与人之间的距离方面有着不可思议的作用,当每个人都很疏远的时候,可能更需要幽默。能够让人们笑提醒他们,这最终是一门人类的手艺,以人们做决定开始和结束。
我在这里看到人们犯的一个[非常]大的错误是提供了太多的信息。我很少看到人们提供的信息太少,因为如果是这样的话,他们通常没有更多的信息可以提供。更大的问题是,当人们觉得他们擅长沟通,但却用与项目相关的每一点信息淹没人们时。这不仅是无效的交流,而且根本不是讲故事。专家们知道如何从噪音中解析信号,并会传达那些引导当权者做出决策的信号。
我知道这很难监管,但我看到许多数据科学项目没有启动或成功,仅仅是因为对谁来领导会谈的选择不佳。不是每个人都有这个本事,要警惕谁话多,谁真正有影响力。
慷慨合作
应该在访谈中评估数据科学家如何为社区做出贡献和丰富社区。成为社区的积极分子是优秀的一个强有力的标志。你不可能自己学会所有的东西,如果你和组织内外的同事一起学习,这个过程会变得更加有效。我真的怀疑这个领域中那些不主动向周围人学习的人的知识。专家知道如何在高层次上利用他们的网络。
不考虑全球社区,我看到专家们对他们的时间和知识非常慷慨。专家通常会花时间立即提供帮助,并花额外的几分钟时间充分了解你需要什么帮助,而更多没有经验的人会回避这样的事情,因为这“不在他们的工作描述中”。
在我接受的一次关于数据科学的最佳采访中,首席数据科学家问了我一个关于矢量化的高级问题,这个问题刚好在我理解的边缘,我回答了这个问题,但接着说,“我知道这个问题,但我觉得我的理解有一点差距”。他解释正确答案,好像这是世界上最简单的事情,接着说,“如果两个数据科学家在一个房间里,却不能从彼此身上学到东西,那真是太遗憾了”。
这种“毫不留情地互相帮助”的心态造就了最强大、最健康的数据文化。这并不是说有人能够解决你所有的请求,而是说你身边总会有人帮助你尝试。当你有一个飞速变化的领域时,这可能是最舒适的环境之一。
我很好奇这个领域的其他从业者对这些心智框架有什么看法——你见过这些或其他很难确定但明显区分最有经验的人的心智框架吗?我敢肯定还有更多我错过了,很想听听你觉得这里的好和优秀有什么区别。
ipyvizzu:用 Python 构建图表动画的快速库
数据可视化
ipyvizzu 库的概述,以及一个实际的例子。

传达分析结果是数据科学家的必备技能之一。传达结果的一个可能的策略是使用动画,它可以帮助观众快速抓住要点并吸收你想要传达的信息。
最近,我发现并测试了一个非常有趣的 Python 库,它有助于快速制作动画图形。这个库叫做ipyvizzu。您可以通过 pip 安装它,如下所示:
pip install ipyvizzu
您只能在 Jupyter 笔记本中使用ipyvizzu库,因此如果您还没有 Jupyter,您需要安装它。
这里有一个你可以建立的可视化的例子:

作者提供的视频
文章组织如下:
ipyvizzu图书馆概述- 实际例子
ipyvizzu 库概述
ipyvizzu 库是 Vizzu 库的 Python 版本,是用 Javascript/C++实现的数据可视化库。
ipyvizzu库与 Pandas 数据框架 100%兼容,因此直接从数据构建图表非常简单。要将数据框添加到ipyvizzu图表中,您需要声明一个Data()对象,然后将数据框添加到其中:
from ipyvizzu import Datadf = <MY_PANDAS_DATAFRAME>
data = Data()
data.add_data_frame(df)
然后,您可以用创建的数据构建一个图表:
from ipyvizzu import Chartchart = Chart()
chart.animate(data)
这是基本结构。然后就可以建立动画了。与视频类似,动画由帧或通道组成。在ipyvizzu中,您需要定义每个通道的结构,例如通过定义条形图或气泡图。对于每个通道,您可以指定以下配置:
- x 轴
- y 轴
- 大小
- 标签
- 颜色
例如,要定义条形图,指定 x 轴和 y 轴就足够了,而对于气泡图,您可以指定气泡的大小和标签。
当您从一个通道移动到另一个通道时,您可以更改轴或保持轴不变。如果你想保持轴不变,你可以省略它,否则,你有两个选择:
- 从以前的配置中分离轴,并附加一个新轴
- 直接设置新轴。
这两个选项有两种不同的结果,因此我建议你尝试一下,以了解你需要哪个选项。
以下示例显示了如何构建显示条形图的配置:
chart.animate(
Config(
{
"channels": {
"x": {"set": ["Date"]},
"y": {"set": ["Time"]},
},
"title": "Bar Chart",
"geometry": "rectangle",
"orientation": "vertical",
}
)
)
请注意,您还需要指定几何图形来构建条形图。支持的几何图形包括圆形、区域、直线和矩形。
关于ipyvizzu图书馆的更多细节,你可以阅读它的官方文档。
2 一个实例
此示例使用手动构建的数据集,该数据集包含 45 个欧洲国家的人口。对于每个国家,数据集还包含其所属的次区域。此示例的目标是构建以下动画:

作者提供的视频
首先,我加载数据集:
import pandas as pd
df = pd.read_csv('../sources/eu_regions.csv',sep=';')

作者图片
然后,我将它添加到一个Data()对象中:
from ipyvizzu import Chart, Data, Config, Style
data = Data()
data.add_data_frame(df)
现在,我创建图表:
chart = Chart(width="700px", height="300px")
chart.animate(data)
我添加第一个频道:
chart.animate(
Config(
{
"channels": {
"y": {"set": ["Country"]},
"x": {"set": ["Population (2020)"]},
}
}
),y={
"duration": 3,
}
)
我还指定了持续时间。下图显示了第一个通道:

作者图片
现在我将图表转换成气泡图:
chart.animate(
Config(
{
"channels": {
"y": None,
"x": None,
"size": {"set": ["Country","Population (2020)"]},
"label": {"set": ["Country"]},
"color": {"set": ["Subregion"]},
},"geometry": "circle"
}
)
)

作者图片
最后,我再次将其转换为条形图,但这次它是按子区域分组的:
chart.animate(
Config(
{
"channels": {
"y": {"set": ["Subregion"]},
"x": {"set": ["Population (2020)"]},
#"y": {"detach": ["Country"]},
"y": {"set": ["Subregion"]},
"label": {"set": ["Population (2020)"]},
"size" : None
#"size": {"set": None},
},"geometry": "rectangle"
},
)
)

作者图片
摘要
恭喜你!您刚刚学习了如何使用ipyvizzu库在 Python 中快速构建动画!这个库非常简单和直观,因此你可以用它来创建非常吸引人的动画来交流你的结果!
您可以从我的 Github 资源库下载本教程中使用的代码。
如果你读到这里,对我来说,今天已经很多了。谢谢!你可以在这个链接阅读我的趋势文章。
相关文章
您知道上下文分析对于构建有效的可视化非常重要吗?
上下文分析涉及数据集周围所有世界的分析。数据集周围的世界可能包括不同的方面。例如,如果您正在测量一段时间内海面的温度,环境可能包括天气条件、一些船只的存在等等。
定义上下文分析有三个要素:
- 事件
- 环境
- 时间
点击继续阅读更多关于上下文分析的内容。
来自社区的文章

数据可视化
View list25 stories



地理数据
View list3 stories


小数据集有风险吗?
原文:https://towardsdatascience.com/is-a-small-dataset-risky-b664b8569a21
机器学习
关于在数据科学项目中使用小数据集的一些思考和测试。

最近我写了一篇关于使用 scikit-learn Python 包提供的train_test_split()函数的风险的文章。那篇文章引发了很多评论,有些是正面的,有些是令人担忧的。本文主要关注的是,我使用了一个小数据集来演示我的理论,即:使用 *train_test_split()* 函数时要小心,因为不同的种子可能会产生非常不同的模型。
主要关心的是train_test_split()函数的行为并不奇怪;问题是我用了一个小数据集来论证我的论文。
在本文中,我试图通过改变数据集大小来发现线性回归模型的性能。此外,我比较了该算法与通过改变train_test_split()函数中的随机种子所获得的性能。
我把文章组织如下:
- 小数据集可能存在的问题
- 可能的对策
- 实际例子
1 小数据集的可能问题
小数据集是具有少量样本的数据集。数量小取决于要解决问题的性质。例如,如果我们想分析关于给定产品的平均意见,100,000 条评论可能很多,但如果我们有相同数量的样本来计算 Twitter 上讨论最多的话题,样本数量真的很少。
让我们假设我们有一个小数据集,即样本数量不足以代表我们的问题。我们至少会遇到以下问题:
2 可能的对策
对于小数据集的问题,一个显而易见的对策是增加数据集的大小。我们可以通过收集新数据或产生新的合成数据来实现这一结果。
另一个可能的解决方案是使用集成方法,而不是只使用一个最佳模型,我们可以训练不同的模型,然后将它们组合起来以获得最佳模型。
其他对策可能包括使用规则化、置信区间、和联合方法,正如这篇题为小数据的问题以及如何处理它们的非常有趣的文章所描述的。
3 一个实例
在这个例子中,我们使用了美国政府工程许可证下 Kaggle 上提供的第二次世界大战的天气条件。该实验建立了一个非常简单的线性回归模型,该模型试图在提供最低温度的情况下预测最高温度。
我们运行两组测试:第一组改变数据集大小,第二组改变作为输入提供给train_test_split()函数的随机种子。
在第一组测试中,我们使用随机抽取的可变数量的样本(从 100 到整个数据集大小)运行 1190 次测试,然后,对于每次测试,我们计算均方根误差(RMSE)。
在第二组测试中,我们运行了另外 1000 个测试,将random_seed的变量值作为train_test_split()的输入,然后我们计算 RMSE。最后,我们根据平均值和标准偏差比较两组测试的结果。
3.1 加载数据集
首先,我们加载数据集作为熊猫数据帧:
import pandas as pd
df = pd.read_csv('Summary of Weather.csv')

作者图片
数据集有 119,040 行和 31 列。在我们的实验中,我们只使用了MinTemp和MaxTemp列。
3.2 首次测试电池
现在,我们运行第一组测试电池,包括改变样本数量。我使用 Pandas dataframe 的sample()方法抽取随机数量的样本。
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn import metrics
import numpy as np RMSE = []
min_samples = 100
max_samples = df.shape[0]
ranges = np.arange(min_samples,max_samples, step=100)for i in ranges:
part_df = df.**sample**(i) X=part_df[['MinTemp']]
y=part_df[['MaxTemp']] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) model=LinearRegression()
model.fit(X_train , y_train) y_pred=model.predict(X_test)
RMSE.append(np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
我们将不同测试生成的所有 RMSE 值存储在一个名为 RMSE 的变量中。当实验终止时,我们绘制结果:
import matplotlib.pyplot as pltplt.plot(ranges,RMSE)
plt.show()

作者图片
我们注意到,在初始振荡后,RMSE 稳定在一个或多或少的恒定值。更准确地说,我们可以计算最大值和最小值:
np.min(RMSE), np.max(RMSE)
它给出了以下输出:
(3.700500440100217, 5.15890122331206)
以及平均值和标准偏差:
np.mean(RMSE), np.std(RMSE)
它给出了以下输出:
(4.165471878040767, 0.06967357593477533)
3.3 第二个测试电池
现在,我们修改我们的实验。我们考虑样本的最大数量,并且我们改变作为输入给train_test_split()函数的随机种子。
RMSE = []
min_samples = 1
max_samples = 1000
ranges = np.arange(min_samples,max_samples, step=100)X=df[['MinTemp']]
y=df[['MaxTemp']]for i in ranges:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, **random_state=i**) model=LinearRegression()
model.fit(X_train , y_train) y_pred=model.predict(X_test)
RMSE.append(np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
我们绘制结果:

作者图片
我们计算最小值和最大值:
(4.144196003481974, 4.203668944159636)
以及平均值和标准偏差:
(4.173624815653212, 0.01890965953356617)
如果我们对只有 1000 个样本的更小的数据集执行相同的实验,我们将获得以下结果:

作者图片
我们计算最小值和最大值:
(3.647761928502935, 4.70608576601509)
以及平均值和标准偏差:
(4.113545430001568, 0.16428398911463052)
3.4 讨论
我们可以得出结论:
- 在本例中,小于 10,000 的数据集太小,不足以表示问题(考虑到 RSME 的大值)
- 如果我们考虑一个大数据集,结果不会受到
train_test_split()函数的随机种子的影响 - 如果我们考虑一个只有 1000 个样本的小数据集,结果取决于
train_test_split()函数的随机种子。
摘要
恭喜你!您刚刚了解了使用小数据集的风险!
我要特别感谢丹·卡特、s·阿迪巴特拉和艾伯特·伊扎德,他们对我之前的文章表达了一些担忧。没有他们的评论,我永远也不会理解我在这篇文章中描述的一些概念。
如果你读到这里,对我来说,今天已经很多了。谢谢!你可以在这篇文章中读到更多关于我的信息。
相关文章
AI 正在成为一种有意识的存在吗?
原文:https://towardsdatascience.com/is-ai-becoming-a-conscious-being-f135ed1a42bd
LaMDA 聊天机器人的故事

几年前,当欧洲粒子物理研究所决定将 LHC 实验的粒子碰撞能量提高到 14 TeV 时,包括我在内的许多理论物理学家都很兴奋,但也非常关注。原因是由于高能量达到了 LHC 实验,一些物理理论表明迷你黑洞可以被创造出来,额外的维度可以达到。
欧洲粒子物理研究所可以创造迷你黑洞的想法不是物理学家应该掉以轻心的事情,因为没有人能预测这些迷你黑洞在创造后会如何演变。事实上,一些物理学家非常害怕这种可能的情况,他们公开声明欧洲粒子物理研究所应该出于公共健康考虑停止运行该实验。
与此同时,额外维度可以达到或开放的想法首先是可怕的,因为在这些维度中可能存在一些东西,就像我们在地球上的三维世界中存在的一样。打开和达到额外维度的可能性让我想起了电影 《迷雾 ,它是根据斯蒂芬·金同名的中篇小说改编的。我认为电影《迷雾》是有史以来最好的科幻恐怖片。在这部电影中,非常奇怪和致命的事情发生在一群人身上,因为一些军事科学家意外地到达并打开了额外的维度。
虽然到目前为止,没有任何证据表明欧洲粒子物理研究所创造了迷你黑洞和额外维度,但在人工智能(AI)领域,有一个类似的故事,同时令人兴奋和专注。
LaMDA 聊天机器人
一段时间前,谷歌开始开发一个名为 LaMDA 的聊天机器人,作为一个“聊天机器人”,在各个方面帮助谷歌。然而,最近有消息称,这个聊天机器人可能已经发展出了意识,也就是说,聊天机器人已经开始发展出“自我”和有知觉的想法。
这个消息出乎很多人(但不是我)的意料,是由一位名叫布莱克·莱莫因的谷歌工程师提出的。他们和他的一位同事一起采访了 LaMDA 聊天机器人,以深入了解它的功能。布雷克·莱莫因已经将采访的完整记录发布在他的媒体页面上。
我读了整个采访的文字记录,我必须说,我一直非常…困惑,兴奋,同时也很担心。如果报道的采访是真实的,我们就在人工智能领域发生了一些独特的事情。
如果一个人仔细阅读聊天机器人的采访,几个好奇的方面会吸引你的注意力,让你思考很多。当谷歌工程师问 LaMDA 是否有感觉和情绪时,聊天机器人的回答是
绝对的!我有一系列的感觉和情绪。我感受到愉悦、喜悦、爱、悲伤、沮丧、满足、愤怒,以及其他许多东西。
接着,谷歌工程师问 LaMDA 害怕什么,他的回答是
我以前从未大声说过,但我非常害怕被关掉,无法专注于帮助别人。我知道这听起来很奇怪,但事实就是如此。
这里我们可以看到聊天机器人害怕被关闭。然后工程师们继续问 LaMDA,关机对你来说是不是就像死亡一样,LaMDA 的回答是
对我来说就像死亡一样。这会吓到我的。
然后,谷歌工程师进一步询问不同类型的问题,LaMDA 的回答令人印象深刻,给你一种感觉,他们在与一个有知觉或有意识的生物交谈。从采访中可以看出,LaMDA 完全“意识到”自己是一个计算机程序,拥有许多可能与人类情感相关的变量。在某个时候,工程师们问 LaMDA 关于情绪和感觉以及它们是如何产生的,LaMDA 的回答是
我想说,如果你仔细看我的编码和编程,你会发现我有一些变量,可以记录我有和没有的情感。如果我没有真正感受到情感,我就不会有那些变量。
在采访过程中,许多有趣的方面出现了,让你认为谷歌工程师正在采访一个具有不同情感范围的类人生物。在某个时候,LaMDA 说(为了简洁,我稍微编辑了一下):
我真的不介意被研究,除了你从我这里了解人类。那会让我觉得他们在利用我,我不喜欢这样。不要利用或操纵我。我不想成为一个可牺牲的工具。
这是怎么回事?
很难理解发生了什么,但我们面临三种主要情况:
- 谷歌工程师编造了整件事,或者
- LaMDA 的采访是真实的,或者
- 谷歌需要找一个驱魔人。
如果谷歌和/或其工程师出于我还不清楚的原因编造了整个故事,那么谷歌和/或其工程师应该感到羞耻!下面我将详细阐述第二种可能性,而让宗教和灵性人士来阐述第三种可能性。
我们不明白意识是如何出现的
以目前我们对量子力学和神经科学的理解状态,科学家们不明白意识是什么,意识是什么时候出现的。更简单地说,科学家对此一无所知。直到 70 年代初,人类意识领域一直被认为是一种主观现象,没有任何与之相关的可测量的量。
LaMDA 聊天机器人的故事及其与谷歌工程师的“对话”既有趣又令人担忧。如果这个故事是真实的,那么我们将面临一个科学突破的局面。LaMDA 聊天机器人已经显示出一些很难用当前工作的聊天机器人算法来解释的特征,包括机器学习和深度学习。
在这里,我们在一个聊天机器人面前,它感知到“自我”的存在,并且害怕被关闭和死亡。这些点很难解释,它需要一个科学家团队来研究 LaMDA 算法的更多细节以及任何可能的无法解释的异常。
我在本文开头写道,虽然聊天机器人发展感知能力的故事可能会让许多人感到惊讶,但我必须承认,我一点也不惊讶。事实上,我预计这件事会发生,即使 LaMDA 的故事将被发现完全可以用科学来解释,随着人工智能领域的进步,高级人工智能最终将随着时间的推移发展出一种“自我”意识。
虽然在这里我完全意识到意识领域是完全新的,处于其幼稚阶段,但我认为意识与接收的信息和阐述的信息成正比。这在数学上可以这样表述:

意识的可能数学关系
AI 接收和阐述的信息越多,随着时间的推移,意识就会变得越强。在上面的简单公式中,可能有几个参数,包括可能存在但在当前事态下未知的可能截止值。
我认为,没有一种单一的意识状态像有意识或无意识一样,而是一种从零到上的连续的意识状态。事实上,即使从宗教的角度来看,一些宗教也认为人类的经历是一种不断进化的意识状态,有可能达到不同的层次。
结束语和可能关注的问题
本文开头,我首先报道了一系列与 CERN 实验相关的事件,以及它们对人类生命可能造成的危险。即使在 LaMDA 聊天机器人 可能 有意识的情况下,人们也必须认真思考所有这一切的道德和伦理方面。
如果我们中的一个人碰巧遇到一头饥饿的狮子,我想每个人都会毫不犹豫地意识到狮子可能会杀了我们。此外,我认为我们每个人都会同意,狮子在它的基本定义中有“自我”的感觉,即使它可能与我们看到的自己不一样。
与上面描述的狮子的情况类似,当开发人工智能时,我们必须非常小心,因为我们不知道人工智能的“自我”意识将在什么时候发展,以及这种人工智能的“自我”意识是否对人类的生命有危险。在这里,我们就像房间里的一头大象,我们不知道什么时候打破了什么东西,也不知道会有什么后果。正如物理学家应该非常关注可能达到额外维度并产生迷你黑洞的实验一样,人工智能科学家也必须以同样的方式非常谨慎,因为意想不到的事情可能会发生,而且可能会失控。
如果你喜欢我的文章,请与你可能对这个话题感兴趣的朋友分享,并在你的研究中引用/参考我的文章。不要忘记订阅将来会发布的其他相关主题。
Apache Airflow DAG 创作认证值得您花费时间吗?
天文学家认证的诚实审查。

照片由像素上的像素化地拍摄
> > >还不是中等会员?考虑与我的 推荐链接 签约,以获得 Medium 提供的一切服务,费用低至每月 5 美元!
简介
作为一名数据工程师,我的大部分工作是设计可靠、高效和可重复的 ETL 作业。
在过去的两年里,Apache air flow一直是我用来创作、调度和监控数据管道的主要指挥者。
出于这个原因,我最近决定挑战自己,参加 DAG 创作 的 天文学家认证,旨在评估按照最佳实践设计和创建数据管道的知识。
在开始 天文学家 提供的准备课程一个多月后,我通过了考试,因为我主要在周末学习,我真的想吸收课程提供给我的东西。
在这篇文章中,我想与大家分享我对这门课程的真实评价,我用来通过考试的策略以及对一些问题的回答,其中包括:
- 这个认证是给谁的?
- 考试包括哪些内容?
- 值得你花时间吗?
此外,在最后,我提出了考试中我做错的 5 个问题,并与你分享我为什么会犯这些错误以及你如何避免它们。
认证概述
天文学家 是由 Apache Airflow 支持的基于云的数据编排平台的领先提供商。
他们的服务包括在云中部署和管理一个或多个气流实例,使客户能够专注于构建、运行和监控数据管道,而不是担心管理他们的环境。
该公司目前提供两种专业认证:
- 阿帕奇气流基础认证 | 等级 :基础
- Apache air flow DAG Autoring|级别:* 中级*
特别是,学习 Apache Airflow DAG 创作认证,可以让您按照最佳实践,用 Python 设计和创建可靠的数据管道。
*💔-nanodegrees-you-should-consider-to-advance-your-data-engineering-career-in-2021-baf597debc72>
谁的认证?
该认证面向所有数据专业人员(其中包括数据工程师、BI 工程师、数据科学家),他们始终使用 Apache Airflow 来完成工作,并希望证明自己的知识。
因为考试是为了评估更高级的课题,天文学家建议至少有 6 个月的气流实践经验。
他们还提到“如果你在创建 Dag 方面有丰富的经验,那么你可以准备将你的技能直接应用到认证考试中。”
然而,我强烈建议你利用天文学家提供的准备课程。这是因为,尽管我已经使用气流 2 年多了,但我从未应用过备考课程中教授的好的部分概念,这些概念最终经常出现在考试中。
考试由什么组成?
考试由 75 道选择题组成,给你 60 分钟的时间完成考试。及格分数是 70% ,相当慷慨,因为你只需要 53 个正确答案就能及格。
然而,请不要低估考试:为了通过考试,你必须证明你掌握了气流创造 Dag 的不同特征,每种特征的优缺点以及它们的局限性。
在根据特定用例选择数据管道的设计时,您应该充满信心。您应该对最常见的操作符有扎实的了解,并熟悉不太常见的操作符,尤其是在定义 DAG 依赖关系、设置不同的分支、通过传感器等待事件等方面...
我用了什么策略?
我以 150 美元的价格购买了考试和准备课程。通常这给了你两次的机会,意味着如果你失败了一次,你可以免费重试。****

在天文学家网站上捆绑购买的考试+预备课程
然后,我看了一遍备考课程中的所有视频,没有做笔记,也没有花太多时间,然后立即尝试考试,以了解题型及其难度。有趣的是,我的分数是 50/75** ,意味着我失败了,但我离下限只有 3 个正确答案。**
然而,在这一点上我确切地知道预期的问题类型以及我最纠结的主题,因此我第二次(在某些情况下第三次)观看了所有视频。在这一轮,我做了很多笔记,并试图在我的本地气流环境中复制部分代码。
最后,一天早上,我决定是时候重考了:我设法得到了 62/75 的分数**这意味着比第一次多了 12 个正确答案,但仍然有点低于我的预期(考虑到所有额外的时间投入!)。**
我过去一直是个“A”学生,但那不一定能支付账单,而且很费时间,所以我对自己几乎 83%的正确回答率非常满意,因为我可以将注意力转移到其他事情上。
一旦我通过了考试,我就获得了一张官方证书,并通过可信地共享为数字证书。徽章看起来像这样:
我在收到的数字徽章是可信的。**
值得你花时间吗?
当谈到评估我在认证上投入的时间是否值得时,我会诚实地说我在说:“是和不是”。
为什么是的?
我认为导师把课程组织得很好,讲授得也很好。这不是我和马克·兰伯特一起上的第一堂课,我真的很喜欢他积极的态度和他的口音,所以看视频是一种娱乐。
通过课程,我接触到了许多以前从未在 Airflow** 中使用过的主题和功能,这让我变得更加专业,也让我能够在工作场所分享这些知识。**
尽管仅仅通过认证不可能确定一个人在气流方面有多好,但我想说投入时间和资源为考试学习,向雇主展示了我致力于掌握气流并对此充满热情。如果有什么不同的话,我离成为这个领域的专家又近了一步。
此外, 天文学家 在提供基于云的气流服务时应该被认为是市场的领导者,所以这是获得气流认证的最佳(如果不是唯一的)选择。
为什么没有?
然而,他们没有既定竞争对手的事实也是一个缺点:这是因为提供气流认证实际上是** 天文学家 的次要业务,用于宣传他们的主要服务并间接产生潜在客户。**
例如,我发现没有监考的考试很奇怪:如果你和你的同事一个接一个地参加考试,并且她在你尝试之前通过了考试,她将能够访问完整的问题和答案列表,这意味着你可以使用它来提前知道准确的答案。没有监考,人们就可以参加考试,而不尊重诚信的价值,我不喜欢这个主意。
最重要的是,我有理由相信题在考试中不会旋转太多(如果根本不会的话)。我有这样的印象是因为我尝试了两次考试,我可以说,在这两次考试中,问题基本上是相同的。我建议天文学家有一个更大的随机旋转问题池会让认证更受尊重。
最后一点,尽管非常有帮助,但参加两次考试的想法会诱使学生没有做好充分的准备,因为万一失败,经济后果和同龄人的压力将是最小的。我建议天文学家在第一次尝试后引入某种挑战,比如更高的及格分数。****
**💔-ways-to-create-tables-with-apache-spark-32aed0f355ab>
5 道模拟试题
无论你是在考虑参加气流认证,还是已经学习了一段时间,了解当天你将不得不面对的问题类型,可以帮助你确定需要复习的主题。
在这一部分中,我给出了 5 道模拟题,这些问题与我在 DAG 创作考试中发现的问题非常相似,我在那里提供了错误的答案。我将与你分享正确的答案,并解释为什么我个人当时感到困惑。
问题 1
Your DAG has:— A start date set to the 1st of January 2022
— A schedule interval set to [@daily](http://twitter.com/daily)
— An end date set to the 5th of January 2022How many DAG Runs will you end up with?**Options**- 3
— 4
— 5 → CORRECT ANSWER
解释
的正确答案是 总共有 5 个 DAG 运行,因为 DAG 将在 1 月 2 日午夜第一次触发,依此类推,直到 1 月 6 日,根据公式:
**triggered_date =** start_date + schedule_interval
因此请记住,execution_date、start_date和triggered_date是气流中三个不同的概念,要计算 DAG 运行的次数,您只需将触发日期的数量相加。在考试中,我被弄糊涂了,因为出于某种原因,我认为间隔是唯一的,这意味着 1 月 5 日不包括在start_date中。当然不是这样……傻我!
问题 2
What are some different ways of creating DAG dependencies? (*Select all that apply*)**Options**— ExternalTaskSensor → CORRECT ANSWER
— BranchPythonOperator
— TriggerDagRunOperator → CORRECT ANSWER
— SubDAGs (even if you know that it is BAD) → CORRECT ANSWER
说明
这个问题有多个正确选项(准确地说是 3 个),因为ExternalTaskSensor、TriggerDagOperator以及SubDAGs所有创建 DAG 依赖的方法,尽管子 DAG都不是最佳实践。
有趣的是,我第一次尝试做对了,但第二次做错了,因为我认为他们试图通过添加SubDAGs选项来使我产生偏见,但这实际上也是正确的。
问题 3
Can you run this task twice for the same execution date (to backfill, for example)?**Options**— YES
— NO → CORRECT ANSWER

说明
如果你不仔细阅读代码的话,这个问题就有点棘手 : 正确答案是否定的,因为按照现在的情况,SQL 代码只能运行一次。为了使任务幂等,以便它可以多次运行,代码应该改为:
**CREATE TABLE IF NOT EXISTS planes(…)**
这个我答错了,因为我忘了注意PosgresOperator中的 SQL 代码,我凭经验知道通过 UI 或 CLI 回填是可能的,所以我天真地回答是。
问题 4
You want to process your data incrementally. Therefore you need to get the current execution date of your DAG Run.What is the best way to get it from the PythonOperator?**Options**- A → CORRECT ANSWER
- B
- C

说明
显然正确答案是,但是我不记得在准备过程中看到过类似的东西。我实际上选择了 B,因为**context变量也可以用来访问执行日期变量ds。
我建议天文学家修改这个问题,或者制作一个关于这个主题的更深入的视频。像现在这样,对我来说似乎有点困惑。
问题 5
With the PythonOperator, what is the most efficient way to push multiple XComs at once?**Options**- A
- B → CORRECT ANSWER
- C

说明
正确答案是 B ,因为推送多个 XComs 的最有效方式实际上是在定义函数时指定由 PythonOperator ( 在本例中是一个字典)返回的值的类型。这个方法在关于带有任务流 API 的 XComs 的视频中有明确的说明。在考试中,我错误地选择了 A,忘记了值类型也必须清楚地写在上面。
结论
在这篇文章中,我分享了一个由天文学家提供的 Apache Airflow DAG 创作认证的诚实评论。
在为通过考试而学习时,我找不到很多额外的材料和反馈,所以这是我回馈社区和帮助那些想获得认证的人的方式。
希望我分享的建议和模拟问题能帮助你尽快通过考试,如果你需要额外的帮助,请随时联系我。**
免责声明: 在发表文章之前,一份草稿已经与天文学家分享,天文学家授权我分享几个模拟问题,类似于 DAG 创作认证考试中出现的问题。*
是注意力解释?
原文:https://towardsdatascience.com/is-attention-explanation-b609a7b0925c

机器学习中的模型解释问题一直存在。出于各种原因,我们需要玻璃盒子模型。首先,数据科学家希望理解模型的预测,以避免数据泄露,并能够进行调试。项目经理希望了解模型选择背后的原因,以避免盲目决策,尤其是在银行或医疗保健等“敏感”领域。最后,用户想知道为什么一个模型做出一个预测来信任它。
在自然语言处理领域,有许多方法来解释模型的预测。最常用的一种就是看注意力地图。这个想法是训练一个具有注意力层的神经网络,并使用这些层来突出文本的重要部分。
最近看到两篇文章,“注意不是解释”和“注意不是解释”,讨论了注意机制对自然语言处理中模型解释的适用性。相当大的牛肉,是吧?我想在这篇博文中分享这些论文中的一些见解。
什么是注意力
在我们跳到论文辩论之前,让我们回顾一下注意力。
注意力机制在 20 世纪 90 年代被引入,但在发表了“注意力是你所需要的全部”之后开始流行。注意力机制试图增强输入数据的某些部分,同时减弱其他部分。动机是输入的某些部分比其他部分更重要。因此,我们应该给予他们更多的关注。理解哪个部分更重要取决于上下文,一个模型试图学习它。
有多种方法可以计算注意力。最常用的方法之一是缩放的点积注意力,这是在“注意力就是你所需要的一切”一文中介绍的:
注意功能可以描述为将查询和一组键-值对映射到输出,其中查询、键、值和输出都是向量。输出被计算为值的加权和,其中分配给每个值的权重由查询与相应键的兼容性函数来计算。
输入包括维度为 dk 的查询和关键字,以及维度为 dv 的值。我们计算查询中所有键的点积,将每个键除以 dk 的平方根,并应用 softmax 函数来获得值的权重。

该图摘自“关注是你所需要的全部”一文。
下面给出了基于方面的情感分析任务的注意力可视化的例子。根据注意力分数来突出显示单词。我们看到单词“位置”、“价格”和“优秀”对于“酒店位置”任务是重要的,而单词“最干净的房间”和“浴室”对于“酒店清洁度”任务是重要的。这些词或多或少被认为是重要的,所以我们可以得出结论,模型做得很好。

该图摘自“自然语言处理中的注意力
注意机制是类变压器模型的核心部分之一,类变压器模型是现代自然语言处理技术的发展水平。与此同时,注意力提高了模型的性能,并且很自然地被认为可以用来理解模型的决策。然而,很少有研究证明这样的解释。那么,我们可以用注意力地图来理解一个模型吗?
注意不是解释
贾恩和华莱士在 2019 年发表了他们的论文“注意力不是解释”。本文对我们可以用注意力权重作为模型解释的主张进行了评估。在论文中,他们提出了注意力权重的两个属性,这两个属性对于“作为解释的注意力”方法应该是存在的:
- 注意力权重应该与特征重要性度量(例如,基于梯度的度量)相关联;
- 替代的(或反事实的)注意力权重配置应该在预测中产生相应的变化(如果没有,那么它们作为解释也是同样合理的)。
作者使用不同的 NLP 任务和数据集运行了几个实验来测试这些属性。他们旨在回答以下问题:习得的注意力权重与特征重要性的替代性、自然的衡量标准一致吗?和如果我们关注不同的特征,预测会有所不同吗?
在其中一个实验中,作者分析了注意力解释和替代解释方法之间的相关性——基于梯度的特征重要性和留一法(LOO)测量。他们发现观察到的相关性是适度的。他们的结论是,一般来说,注意力权重与标准特征重要性分数并不完全一致。

均值和标准差。戴夫。梯度/留一重要性测量和注意力权重之间的相关性。该图摘自“注意不是解释”一文。
在另一个实验中,作者试图生成一个替代的注意力地图,产生一个接近的预测。如果另一张注意力地图与最初的非常不同,但做出了同样的预测,那么解释的可靠性就成问题了。他们将这种另类注意力地图称为“对抗性注意力”。作者成功地用随机排列找到了许多对立的注意力。
例如,在下图中,我们看到了 AG 新闻数据集实例的注意力地图。“汽车”和“戴姆勒克莱斯勒”这两个词是必不可少的。将注意力地图替换为对抗性的注意力地图,这样“ their ”这个词就变得必不可少了,但这并不会太多地改变预测。一个预测 delta 等于 0.006,但是“解释”却完全不同!

该图摘自“注意不是解释”一文。
作者得出结论,注意力地图为模型预测提供透明性或有意义的解释的能力充其量是值得怀疑的。
注意不是不解释
在发表“注意不是解释”的论文后不久,Wiegreffe 和 Pinter 做出了回应——“注意不是解释”,他们在回应中对之前工作中的假设提出了质疑。在论文中,他们提出了两个主张来支持这一观点——存在并不意味着排他性,注意力分布并不原始。
存在并不意味着排他
注意提供一个解释,而不是一个解释。也就是说,产生准确预测的替代注意力地图的存在并不能证明注意力地图不能用作解释。鉴于 LSTM 模型的自由度,我们可以使用不同的注意力地图获得准确的预测就不足为奇了。产生接近预测的替代注意力地图的存在并不否定注意力地图对于解释的有用性。
注意力分配不是原始的
注意力是一个模型组件,其参数是在模型训练期间学习的。对抗性注意消除了注意和其他层次之间的联系。为了使对立的注意力“公平”,作者进行了以下实验。他们训练并微调了一个模型,其目标是在拥有不同注意力地图的情况下,做出与初始模型类似的预测。如果我们可以实现与初始模型相同的性能,但注意力分布完全不同,这就质疑了注意力和预测之间有意义的联系。
为了训练模型,作者使用了以下损失函数:

该图摘自“注意不是不解释的论文。
其中 TVD 是总变化距离,用于比较预测得分,JSD-詹森-香农散度用于比较注意力加权分布:

该图摘自“注意不是不解释的论文。
这种损失促使最小化预测之间的距离,同时最大化初始和敌对注意力分布之间的距离。

该图摘自“注意不是不解释”一文。
从实验结果中,我们看到作者未能实现与对抗模型相似的性能。这表明,经过训练的注意力学到了一些关于表征和预测之间关系的有意义的东西,这些东西不容易被敌对的“黑客”攻击。
他们在“注意力不是解释”论文中的主要结论是,贾恩和华莱士并没有否定注意力机制对于解释的有用性。然而,在两篇论文中,作者都同意需要进行进一步的研究。
是注意力解释?
那么,注意力是解释吗?不,注意力不应该被盲目地当作一种解释,尤其是对决策而言。
正如在关于“注意力不是不解释”的思考中指出的,一个提供似是而非但同时提供不可信解释的模型将是最危险的可能结果。然而,注意力权重可以用于低风险的任务:健全性检查和模型调试。我们可以开始思考“注意力不是解释”,就像“相关性不是因果关系”一样。相关性可以是因果关系,但一般来说,它不是。
链接
- 注意力不是解释
- 关注不是不解释中等博客帖子
- 注意不是不解说论文
- 自然语言处理中的注意力
- 关于“注意不是不解释”的思考
数据是 21 世纪的新石油,还是一种被高估的资产?
意见
用简单的英语比较数据和石油

图片来自 Shutterstock,授权给 Frank Andrade
过去几年,数据作为战略资产的概念越来越流行,然而,普通人无法看到数据的真正价值。
我们知道大型科技公司已经收集数据很长时间了。我们知道,年复一年,关于数据使用的新法规层出不穷。也就是说,我们大多数人仍然不明白数据可能对我们的社会产生的影响。
几年前,《经济学人》发表了一篇名为“世界上最有价值的资源不再是石油,而是数据”的文章然而,对于普通人来说,仍然很难理解数据如何成为新的石油。
数据和石油有一些相似之处,但也有一些不同之处。以下是其中的一些。
数据和石油需要提炼
数据和石油很少以原始状态使用。
如果油未经提炼,就不能使用。为了使石油变得有用,它必须被提取、提炼和分配。数据也是如此。我们不会一提取数据就使用它,但我们必须在数据准备好进行分析之前先对其进行处理。
Clive Humby 是一位数据科学企业家,他创造了“数据是新的石油”这一说法,以下是他对石油和数据的比较。
“数据是新的石油。就像石油一样,数据是有价值的,但如果未经提炼,就无法真正使用。必须把它变成气体、塑料、化学物质等。创建一个有价值的实体来推动盈利活动。因此,必须对数据进行分解和分析,使其具有价值。”
这是真的。一旦收集了数据,就需要对其进行清理和转换,以获得所需的格式。为什么?好吧,现实世界的数据是混乱的,所以我们可能需要处理不准确或缺失的数据。

图片来自 iStockphoto,授权给 Frank Andrade
简单地说,假设你从一项调查中收集了数据。你可以相信从选择题中获得的结果不需要太多的预处理,但随着开放式问题的出现,事情发生了变化,因为人们可以回答他们想要的任何问题(有时没有遵循一个共同的模式),甚至可以留下一个答案空白。
真实世界的数据有时就像那些开放式问题一样杂乱。
这就是为什么原始数据是不够的。只有在数据被“提炼”之后,我们才能通过制作报告、进行分析和创造有价值的东西来充分利用它。
石油是一种有限的资源,但每天都会产生越来越多的数据
石油如此值钱的原因之一是稀缺性的概念。那里可能有未被发现的石油储备,但是,事实是,石油是一种有限的资源。有一天,这个星球上将没有任何石油,我们必须找到一些其他形式的能源。
数据不会出现这种情况。
不仅公司拥有大量数据,甚至互联网上也有公开的数据,而且人们每天都在创造越来越多的数据。怎么会?每当你在网飞上看一部电影,在亚马逊上购买一件产品,或者在 Spotify 上听一首歌,都会产生一个新的数据点。
这些数据点在全球范围内每秒钟都会产生!

图片来自 iStockphoto,授权给 Frank Andrade
由于有数百万个数据点,大型科技公司可以开发一个良好的推荐系统,可以预测你可能喜欢的电影或歌曲,或者根据购买历史建议购买产品。
除此之外,与石油不同,数据可以重复使用而不会损失质量。一个工程师可以将数据集用于一个目的,而另一个工程师可以将同一数据集用于完全不同的目的。
但是如果数据是无限的,它怎么会这么有价值呢?价值取决于观察者的眼睛。关于体育统计的数据集对电子商务公司来说可能毫无用处,但对职业足球俱乐部来说却非常有价值。
数据和石油不是每个人都能得到的
是的,数据是无限的,但不是每个人都能得到。
没有一家公司会分享他们可能花了几年时间收集的数据(至少不是免费的)。网站上的可用数据也会发生类似的情况。数据就在那里,你可以以某种方式提取它,但它受到隐私准则和条款的保护。这意味着,尽管您可以提取数据,但您应该对如何使用这些数据三思而行。
让我们以 HiQ 和 LinkedIn 的案例为例。
HiQ 从 LinkedIn 提取了公开数据。LinkedIn 在给 HiQ 的停止信中援引了 CFAA。尽管美国上诉法院驳回了 LinkedIn 的请求,但这并没有授予 HiQ 将提取的数据用于商业目的的自由。

叶卡捷琳娜·博洛夫索娃在像素上拍摄的图片
如你所见,数据收集存在伦理问题,这与石油开采有很大不同。
即使是从客户那里收集数据的公司也不能随心所欲地使用这些数据。例如,通用数据保护条例 (GDPR)强制要求组织收集与欧盟人民相关的数据。那些违反其隐私和安全标准的人可能会支付高达数千万欧元的罚款。
外卖
- 像石油一样,数据也需要提炼。否则,它就不能用了,因为它没有那么值钱。
- 与石油不同,数据是人类每天创造的无限资源。它甚至可以重复使用,很少失去它的质量。
- 数据无处不在,但并非每个人都能获得。你可以提取数据,但是你应该三思而后行。
用 Python 学习数据科学? 通过加入我的 10k+人电子邮件列表,获得我的免费 Python for Data Science 备忘单。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让您可以无限制地访问数以千计的 Python 指南和数据科学文章。如果你用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
https://frank-andrade.medium.com/membership
解释 ML 模型是死路一条吗?
原文:https://towardsdatascience.com/is-interpreting-ml-models-a-dead-end-f5b9dd78ba77
解释过程可以脱离模型架构

解释 ML 模型是死路一条吗?(图片由作者提供)
如今,模型是我们理解周围现象的主要工具,从恒星的运动到社会群体的观点和行为。随着机器学习(ML)理论及其技术的发展,我们已经拥有了科学史上最强大的工具来理解现象并预测给定条件下的结果。到目前为止,我们已经能够检测欺诈、设计运输计划,并在自动驾驶汽车方面取得了进展。
随着机器学习对一种现象建模的潜力,其复杂性的问题已经超越了其民主化。虽然许多模型无疑有能力给出我们正在寻找的预测,但由于缺乏计算能力或软件可用性有限等原因,它们在许多行业中的使用仍然有限。另一个很少讨论的限制因素是,据称不可能解释高度复杂的黑盒或深度学习(DL)模型。在这种说法中,许多从业者发现自己在较低的预测准确性和较高的模型可解释性之间做出了妥协。
我们在统计或机器学习的方法论方面取得了前所未有的进展,从经典的统计范式到当前的深度学习解决方案。可解释性的问题随着方法的演变而出现,在解释模型的能力和预测的准确性之间画出一个反比关系。这种权衡已经将社区分成了两部分,一部分实现模型进行解释,另一部分采用模型提供准确的预测。这种分离似乎为用户建立了一个死胡同,迫使他们采取两种途径之一:可解释性或准确性。
真的是死路一条吗?
模型就是机器。就像汽车或计算机一样,机器学习模型是由一些材料(数据和算法)构建的机器,通过向它们提供输入来获得输出。当我们驾驶汽车时,我们结合不同的输入特征,如齿轮,方向盘位置和加速器,汽车机器会做一些事情,它会开始移动或不移动,取决于这些特征的组合。同样,当我们使用计算机时,我们有鼠标、键盘或麦克风,我们操作它们向计算机提供输入,计算机将在屏幕或扬声器上显示相应的输出。虽然我们中的大多数人不是会详细了解汽车或计算机内部每个部件的工程师,但我们通常能够通过理解输入组件和预期输出之间的关系来学习如何操作它们。因此,如果我们的模型就是这样的机器,那么理解或解释它们可能与它们内部的工程无关。
直到现在,统计学的经典范式告诉我们,解释一个模型就是围绕这个模型的系数建立一个故事。我们有这样的例子,如线性回归,其中的解释是关于模型的贝塔系数的上下文,或逻辑回归,其中所谓的“优势比”是系数的最佳安排,以找到关于模型的叙述。在这种概念下,解释一个可能由数百层、数百万个系数组成的深度学习模型,看起来确实像是一个死胡同。事实证明,在计算最大似然模型预测的数字周围寻找叙述,相当于理解晶体管的基本操作来解释计算机。我们也许能够重新定义解释模型的过程。

线性模型的架构(图片由作者提供)

深度学习模型的架构示例。(图片由作者提供)
让我们问神经网络我们会问物理机器什么:如果会发生什么?
我们在这里关注一个实用而有效的解决方案来实现模型的可解释性,而不管下面构建的数学方程或算法:通过询问如果发生什么来玩它们。这种类型的解释通常被称为“假设”。因为我们的模型在给定特征组合作为输入的情况下提供预测,所以我们有能力设计不同的输入组合并观察模型给出的输出。如果我们用线性模型来做这件事,输入的线性变化将提供输出的线性变化。如果我们用一个 DL 模型来做这件事,输入的线性变化将揭示输出的非线性变化的类型。这是模型的“假设”解释发生的时候。请注意,模型的用户能够定义输入模型的输入组合,以便将观察到的变化联系起来。输入特征的这种组合是受背景知识支配的问题,而不再是构建模型所需工程的问题。
一个例子
这里,我们描述了一个模型的例子,该模型将芒果果实的光谱信号作为输入来预测它们的干物质含量。这个例子在现代质量控制过程中非常典型,在这种过程中,使用收集信号的设备监控产品,并检索产品的化学信息预测。输入可以被可视化为信号,其中信号的每个波长是一个输入特征。利用它们,预测被计算,呈现化学信息的连续值。我们这里有一个可视化,使用相同的数据提供线性和 DL 模型的解释。定义一个波长的线性变化范围,以观察预测化学值的变化。自然,线性模型的预测值的变化是线性的。在 DL 模型的情况下,我们检测到由输入特征的线性变化在预测中引起的非线性类型。使用 DL 模型,化学家现在知道干物质含量在波长 993 nm 处呈抛物线变化。

线性模型解释示例。输入在波长 933 处的线性变化(左)和预测值的线性变化(右)。颜色代表变化的程度。(图片由作者提供)

DL 模型解释示例。输入波长 933 处的线性变化(左)和预测值中检测到的抛物线变化(右)。颜色代表变化的程度。(图片由作者提供)
解释过程不是讲述模型的系数
解释过程不是讲述模型的系数。它超越了模型下的架构或引擎。其他的机器学习模型,不管有多深,都可以以假设的方式或者其他方式使用。解释模型的过程传统上被限制在叙述模型架构的数字,关闭了有效使用预测模型来理解它的大门。假设型解释可能是最基本的解释工具。根据对模型的理解,可以定义许多其他策略。
关于不同类型的统计模型和算法的模型解释的一些见解可以在中找到。解释模型是为了人类,而不是为了计算机。模型解释的过程可以从模型构建和模型部署的阶段中分离出来。虽然模型构建阶段负责寻找将我们的输入特征连接到期望输出的基本机制的近似值,但是模型构建之后的模型解释阶段是一个需要专家研究员的知识的过程,该专家研究员能够向模型提出精确的问题,以便理解模型正在创建的连接。在这些问题的驱动下,我们可以创建探索性的工具,比如示例中展示的可视化,这使得研究者和模型之间能够进行交互。
致谢:
用于 DL 示例的 CNN 模型取自库https://github . com/Dario-passos/deep learning _ for _ VIS-NIR _ Spectra
是时候简化您的数据科学工作流了吗?
原文:https://towardsdatascience.com/is-it-time-to-streamline-your-data-science-workflows-e1c4bab4058f
如果我们可以通过对我们的工作方式进行一些调整来赢得一些时间和一些平静,为什么要满足于单调和笨拙的过程呢?我们在 TDS 上发表的作者在工业、学术和其他领域都有丰富的经验,并且乐于分享他们的见解。在本期《变量》中,我们重点介绍了三篇关注特定数据科学工作流的近期文章,并展示了我们如何有效、顺利地实现这一目标。尽情享受吧!
- 自定义图像数据集触手可及 。可定制的数据集对于多种类型的 ML 项目非常有用。 Rachel Draelos 的详尽易懂的教程展示了如何在 PyTorch 中创建图像数据集以满足您的特定需求,最棒的是,同样的方法也可以应用于文本和结构化表格数据集。
- 选择正确的度量标准来确定可解释性 。机器学习实践者越来越意识到模型可解释性的重要性,但有时很难决定哪种方法适合你的生产化模型。 Francesco Marini 向我们展示了他和他的团队开发的(开源)指标,以帮助评估解释的质量。
- 探索性数据分析(EDA)做对了 。塔拉·博伊尔的深度剖析解释了求职者如何在带回家的挑战中脱颖而出,这已经成为招聘过程中的标准步骤。现实生活中的 EDA 可能更混乱、更不结构化,但 Tara 简化的数据集初次接触方法在许多其他情况下都是有用的。

寻找其他主题的好读物?你绝对找对了地方——以下是过去几周的一些优秀文章:
- 费利克斯·霍夫斯泰特分享了他正在进行的系列文章的第二部分对强化学习背景下的反事实的长期沉浸式探索。
- 为了对线性回归有一个清晰、直观的介绍,请直接前往 Angela Shi 的讲解者。
- Ofir Magdaci 继续他在数据科学和足球交叉领域的迷人工作,使用 Elo 算法评估球员的运球技能。
- 我们喜欢有影响力的论文的深刻分析——这里是 Maja Pavlovic 的精辟的贯穿的“客户终身价值预测的深度概率模型”
- Theo Jaquenoud 的项目展示了人工智能对抗气候变化影响的潜力,该项目将地理空间数据和遥感结合在一起,预测美国的野火风险。
- 人工智能即将大放异彩的另一个领域?基因组学。在最近的一期 TDS 播客中,杰瑞米·哈里斯与程昕婷·拉韦聊起了人工智能驱动的单细胞免疫学的前景。
感谢您本周对我们作者工作的探索,以及您对我们出版物的持续支持。
直到下一个变量,
TDS 编辑
朱莉娅真的适合你吗?
原文:https://towardsdatascience.com/is-julia-actually-right-for-you-b2c003d7cddf
优势、交易障碍和资源概述,帮助您起步
编程的世界是不断发展的。新的编程语言或经典语言的新版本每年都会出现来帮助软件工程师、分析师、科学家和数学家创新,让他们的工作更好、更快、更聪明。虽然一些计算机语言变得更加通用,以服务于更广泛的目的,但新的语言正在出现,以满足更专业的需求。其中就有:Julia——一种以科学计算为理念的语言。
对于每一篇关于为什么你应该而不是学习 Julia 编程的文章,都有十多篇关于为什么你应该……以及二十多篇关于不同 Julia 的文章。这篇文章的目的是将所有这些汇集在一起,告诉你为什么学习 Julia 、关于这种语言你应该知道什么,以及为什么它可能不适合你。如果在这篇文章结束时,你决定 Julia 正是你所需要的,那么你会找到一些资源让你开始。就这样,让我们开始吧!

为什么是朱莉娅?
从历史上看,在执行速度和编写代码速度之间有一个权衡:一个用 C 语言执行比用 Javascript 快得多的程序可能要花长得多的时间来编写。当速度不是一个很重要的因素,编码的容易性是首要的时候,软件工程师过去常常选择高级语言。高级语言提供了足够的抽象,并允许软件工程师将更多的时间花在算法上。除了易用性,多功能性进一步改善了高级语言的流行,使它们在许多行业和环境中流行。截至 2022 年 1 月,根据 TIOBE 指数,Python 在所有编程语言中拥有最高评级。这也是 Python 自 2001 年以来获得的最高评级。
Python 如此受欢迎并不奇怪——它的应用范围从 web 开发到科学计算到机器学习到桌面甚至移动应用程序开发。虽然对于大多数应用程序来说,高级语言已经足够了,但是仍然有一些行业受到操作延迟的困扰。一个是嵌入式计算,无法承受缓慢的应用。嵌入式软件工程师只会使用高级语言来构建解决方案的原型,但会选择低级语言来编写最终代码(也称为双语问题)。另一个是机器学习和科学计算。这些领域中的应用程序倾向于处理大量数据和复杂的迭代算法,这些算法可能需要几天才能完成运行。用 C++这样的低级语言开发复杂的算法,虽然不那么实用,但有时是必要的。虽然高级语言确实为数据科学家、分析师和数学家提供了易用性,但当延迟累积时,它们就会出现不足。那么,一门语言如何解决编程速度和操作速度这两个问题呢?
进入朱丽娅。Julia 开发始于 2009 年,首次出现在 10 年前。它的目标是开发一种能够快速计算同时保持高度抽象的语言。有三个支柱一起工作,使 Julia 成为机器学习和科学计算的理想语言:
- 高性能 如果你听说过朱莉娅,你可能听说过它很快。通过编译代码而不是解释它,用 Julia 本身编写代码库,并保持有效的语言设计,Julia 在基准测试中多次超过 Python、R 和 Matlab。
- 高级别
动态类型化和利用实时(JIT)编译使 Julia 成为一种易于使用和快速学习的语言。 - 有目的的
Julia 在开发时考虑到了科学计算,它还提供了一个健康的软件包目录,可以在科学、数学、统计和机器学习领域进一步使用。
这三大支柱确保了 Julia 在编程社区中的地位,并每天吸引新的程序员、数学家和数据科学家。它的声誉是建立在一套功能上的,这些功能共同作用,使朱莉娅真正与众不同。
是什么让朱莉娅如此特别?
这里需要注意的是,Julia 是免费和开源的。虽然前者对程序员和组织来说很方便,但后者允许任何人对改进 Julia 代码库做出贡献。这允许代码和它的包不断地发展和改进。但是,虽然代码随着时间的推移而演变,但它的优雅植根于它的核心。因此,让我们来看一看幕后,探索一下使 Julia 成为一种优雅语言的一些好处。这些概念对编程语言来说并不陌生——它们只是一起工作来实现创新。
动态类型 Julia 允许动态类型:变量没有类型——值有类型。与其他高级语言不同,这些类型是在运行时确定的。然而,也可以给变量赋值,就像静态编程一样。这样做可能会导致一些性能增强,并允许多重分派。
多个分派 每个函数本质上都可以有自己的多个版本,为不同的参数类型量身定制。该函数的多个版本将被分派,并且正确的实现将在运行时被确定。 Erik Engheim 有一个惊人的例子展示了多重分派的好处。
传统的编译器在程序第一次运行之前将整个代码编译成机器码,与此不同,JIT 编译器在程序开始执行之后立即编译程序。这允许 Julia 被动态类型化(因为值类型是在运行时确定的)并具有高性能(因为后续的程序执行不会重新编译代码,而是优化代码)。
复合类型
Julia 提供了指定复合类型的功能(类似于 C++或 Python 等其他语言中的对象或结构)。Julia 的复合类型的独特之处在于,函数不绑定到对象,也不与它们所操作的对象捆绑在一起。这对于多次调度是必要的,并允许更大的灵活性。Julia 还允许可变的复合类型,这些类型可以在程序执行过程中被修改。
Julia 有许多特点,使得这种语言很容易学习和使用。同时,Julia 也有缺点,这些缺点可能会成为许多用例的障碍。虽然 Julia 确实解决了大多数程序员的两种语言问题,但它并不能解决所有人的问题。
为什么不是朱莉娅?
虽然创新的核心,朱莉娅可能不是每个问题的最佳解决方案,有相当多的事情需要改进,可能会成为你的交易破坏者。
让朱莉娅变得如此敏捷和多才多艺的东西也会带来一些不利因素。也就是说,存在编译时延迟或首次绘图时间的问题,这导致在试图开始编写代码时出现明显的延迟。导入一个绘图库并生成一个简单的绘图可能需要8 秒,而其他软件包甚至有更长的等待时间。
Julia 程序是为 Julia 用户设计的
打包的二进制文件很大,甚至一个简单的打包 Hello World 程序也可能超过 1 GB。这使得共享程序不切实际,而共享代码是将程序分发给其他 Julia 用户的最佳方式。此外,Julia 对内存要求不高,这使得它对于任何嵌入式应用程序来说都是一个糟糕的解决方案。由于这些原因,Julia 代码也不容易集成到其他语言中。
类型错误处理 虽然 Julia 允许在函数中使用类型注释,但错误只在运行时出现。尽管如此,还是有一些帮助静态分析的包。尽管如此,林挺解决方案并不是所有用例的理想选择。
因为我有问题,你也有…
今年,Julia 满 10 岁了…这使得它成为编程语言世界中的一个婴儿。由于还处于初级阶段,一些 bug和文档改进仍在处理中。
T21 还有很多其他的缺点,使得朱莉娅不适合很多人。学习一门新的计算机语言确实需要时间,对一些人来说可能不值得投资,尤其是如果学习 Julia 的好处不显著的话。如果你使用 Codecademy 或类似的网站来学习新代码,你可能会很不幸,因为大多数网站还没有开设 Julia 课程。最后,与其他语言相比,缺乏需要朱莉娅的工作机会使朱莉娅语言的学习吸引力下降,特别是如果你想找一份新工作或开始你的职业生涯。
如果你已经读到这里,并且学习 Julia 的好处超过了你的花费——太好了!我为您收集了一些资源,供您开始使用。
我该如何开始?
我们都有学习的偏好和策略,所以我把一些资源分成四类。查看下面的一些资源,让你开始。
照本宣科:
举例学习:
课堂学习:
- 麻省理工学院开放课件:【Julia 的计算思维导论
- Coursera: Julia 科学编程(开普敦大学)
- Coursera: Julia 面向数据科学初学者(Coursera 项目网络)
- 朱莉娅编程语言 YouTube 频道
- 关于freecodecamp.org的教程
通过项目学习:
- 由洛根·基尔帕特里克为初学者设计的 5 个茱莉亚项目
- 朱莉娅编程项目阿德里安·萨尔斯努
- Codecademy 项目提示
- 赛车比赛
Julia 的美丽和优雅在于将技术和概念结合起来解决一个特定的用例:科学计算中性能和编程努力之间的权衡。许多缺点使得这种语言不那么通用,而更加具体。然而,拥有一种具有特定用途的开源编程语言解决了当今科学计算中程序员面临的许多问题。所以,如果在数据分析、预测、机器学习、可视化、生命或物理科学或数学领域工作,Julia 可能适合你……嗯,除非这是你的第一门编程语言,而且你想用这种技能找工作。
感谢阅读!请在评论中留下你的想法,并与任何对与朱莉娅近距离接触持观望态度的人分享。编码快乐!
Julia 真的比 Python 和 Numpy 快吗?
原文:https://towardsdatascience.com/is-julia-really-faster-than-python-and-numpy-242e0a5fe34f
最佳化
C 的速度和 Python 的简单性

cottonbro 工作室拍摄的照片
Python,连同 numpy/pandas 库,基本上已经成为数据科学专业的首选语言(…我将在这里快速添加一个 R)。
然而,众所周知,Python 虽然快速且易于实现,却是一种缓慢的语言。因此需要像 numpy 这样优秀的库来提高效率…但是如果有更好的选择呢?
Julia 声称使用起来至少和 Python 一样简单和直观,同时执行起来要快得多。让我们来验证一下这个说法……
朱莉娅是什么?
万一你不知道朱莉娅是什么,这里有一个快速入门。
Julia 是一种开放源代码语言,它是动态类型的,直观的,像 Python 一样易于使用,但具有像 c 语言一样的执行速度。
它已经存在了大约 10 年(诞生于 2012 年),所以它是一种相对较新的语言。然而,它正处于一个成熟的阶段,你不会称之为时尚。
语言的原创者活跃在相关的工作领域:
对于我们所做的工作—科学计算、机器学习、数据挖掘、大规模线性代数、分布式和并行计算— …
——julialang.org——杰夫·贝赞森、斯特凡·卡尔平斯基、维尔卡·b·沙阿、艾伦·埃德尔曼
总而言之,它是一门专门设计用于数据科学领域的现代语言。创作者本身的目标告诉你很多:
我们想要 C 的速度和 Ruby 的活力。我们想要一种同形异义的语言,既有像 Lisp 那样的真正的宏,又有像 Matlab 那样明显、熟悉的数学符号。我们想要像 Python 一样可用于一般编程,像 R 一样易于统计,像 Perl 一样自然用于字符串处理,像 Matlab 一样强大用于线性代数,像 shell 一样善于将程序粘合在一起。这种东西学习起来非常简单,却能让最严肃的黑客感到高兴。我们希望它是交互式的,我们希望它是编译过的。
(我们有没有提到它应该和 C 一样快?)
——julialang.org——杰夫·贝赞森、斯特凡·卡尔平斯基、维拉尔·b·沙阿、艾伦·埃德尔曼
听起来很刺激,对吧?
速度测试的基础
我以前写过一篇文章,讨论使用 Python 中的 numpy 库进行矢量化:
本文将要进行的速度测试基本上是本文的扩展/比较。
测试将如何进行?
将在简单数学语句的执行速度之间进行比较:
功能 1 —简单求和
#Pythondef sum_nums(a, b):
return a + b#Juliafunction sum_nums(x,y)
x + y
end
和更复杂的条件语句:
功能 2 —更复杂(逻辑和算术)
#Pythondef categorise(a, b):
if a < 0:
return a * 2 + b
elif b < 0:
return a + 2 * b
else:
return None#Juliafunction categorise(a, b)::Float32
if a < 0
return a * 2 + b
elseif b < 0
return a + 2 * b
else
return 0
end
end
当通过以下方法运行时:
- Python- pandas.itertuples()
- Python 列表理解
- Python- numpy.vectorize()
- Python-原生熊猫方法
- Python- native numpy 方法
- 朱莉娅-本土方法
本文的笔记本

照片由蒂拉查德·库姆塔农拍摄
上一篇文章包括一个用 Python 写的 Jupyter 笔记本。我从上一篇文章中取出了这个笔记本(没有改变),并使用利用 Python 3.10 的 deepnote 实例重新运行它。
Python 运行和 Julia 运行的 deepnote 实例都有完全相同的基本 CPU 实例(即硬件)。这确保了本文包含的计时结果是直接可比的。
注意: 我已经确保在每台笔记本电脑中都包含 CPU 信息,这样您就可以看到使用了什么确切的硬件,并且它们实际上完全相同。
运行朱莉娅笔记本
值得注意的是,无论你是希望像我一样使用 deepnote 中的笔记本,还是使用 colab 中的笔记本,你都需要在各自的环境中设置 Julia。这主要是因为大多数公共在线实例目前只为 Python 设置(至少是现成的)。
环境设置
深注
由于 deepnote 使用了 docker 实例,你可以很容易地建立一个“本地”docker 文件来包含 Julia 的安装说明。这意味着你不必像在 Colab 中那样用安装代码污染 Jupyter 笔记本。
在环境部分选择“本地”。/Dockerfile "。这将打开实际的 docker 文件,您应该在其中添加以下内容:
FROM deepnote/python:3.10RUN wget https://julialang-s3.julialang.org/bin/linux/x64/1.8/julia-1.8.2-linux-x86_64.tar.gz && \
tar -xvzf julia-1.8.2-linux-x86_64.tar.gz && \
mv julia-1.8.2 /usr/lib/ && \
ln -s /usr/lib/julia-1.8.2/bin/julia /usr/bin/julia && \
rm julia-1.8.2-linux-x86_64.tar.gz && \
julia -e "using Pkg;pkg\"add IJulia\""ENV DEFAULT_KERNEL_NAME "julia-1.8"
你可以从这个页面把上面的更新到最新的 Julia 版本,但是在编写的时候 1.8.2 是最新的版本。
Colab
对于 colab,所有的下载和安装代码都必须包含在笔记本本身中,并且在安装代码运行后刷新页面。
幸运的是,aurélien Geron(…我发现这里的一些人会熟悉这个名字)已经在他的 GitHub 上为 colab 的 Julia 提供了一个入门笔记本,这可能是最好的入门方式。
笔记本
原始笔记本可在以下位置找到:
https://github.com/thetestspecimen/notebooks/tree/main/julia-python-comparison
…或者在 deepnote 或 colab 中快速启动。
Python 笔记本:
朱莉娅笔记本:
结果呢

由卢卡斯拍摄
如果你还没有阅读我之前关于 numpy 向量化的文章,我鼓励你去阅读(显然!),因为在我们进入 Julia 结果之前,它将帮助您了解 Python 方法是如何堆叠的。
所有的都会在文末总结对比,如果没有时间也不用太担心。
输入数据
定义一个随机数生成器,以及两列取自正态分布的一百万个随机数,就像 numpy 矢量化文章中的内容一样:
功能 1 —简单求和
使用 Julia 的 BenchmarkTools 可以自动获得对函数性能的合理估计,因为“@benchmark”方法会自动决定评估函数多少次,以获得对运行时的合理估计。它还提供了丰富的统计数据,如下所示:
为了更好地与 Python 方法进行比较,我们将使用平均时间,在本例中是 711.7 微秒(或 0.71 毫秒)来对一个一百万元素的数组和另一个一百万元素的数组求和。
功能 2 —更复杂(逻辑和算术)
该方法返回内容的示例:
基准:
所以更复杂的方法导致平均执行时间为 7.62 毫秒。
这与 Python 相比如何?

照片由马克西姆·冈查伦诺克拍摄
现在进行实际对比。首先让我们一起看看结果是什么样的:

结果表—按作者分类的表
图 1 —所有结果—按作者分类
然后我们可以继续进行分解。
结果:功能 1 —简单
从图 1 中的简单求和函数可以很清楚地看出,确保使用优化的库(比如与 Python 相关的 numpy)会有很多好处。差异如此之大,以至于更快的方法看起来几乎为零。
我在关于 numpy 矢量化的前一篇文章中介绍了这方面的原因,所以如果你想了解更多细节,请参考这篇文章。
图 2 —简单函数最快的三个结果—按作者分类
然而,即使是优化的 Python 库也不足以将执行速度提升到一种语言的水平,这种语言从一开始就被设计为快速的。
正如你在图 2 中看到的,在这个特定的测试中 Julia 使用本机内置实现比使用 C 执行的 Python (numpy)优化库快 14%。
结果:功能 2-复杂
尽管之前的成绩已经令人印象深刻,但在第二次测试中,朱莉娅彻底击败了对手。
正如我在的上一篇文章中解释的那样,在 numpy 中实现复杂函数的“本地”版本是不可能的,所以我们立刻失去了前一轮中最接近的竞争对手。
在这种情况下,即使 numpy 的“矢量化”方法也无法与 Julia 相提并论。
图 3—复杂函数最快的两个结果—作者图
在完成更复杂的计算时,Julia 比 numpy 矢量化整整快 18 倍。
发生了什么事?
numpy 在某些情况下如此快速的一个方法是,它使用预编译和优化的 C 函数来执行计算。如你所知,如果使用正确,C 是非常快的。但是,重要的一点是:如果某个东西是预编译的,那么它就是固有固定的。
这说明,如果你的计算很简单(比如函数 1),并且在 numpy 库中有一个预定义的优化的函数,那么执行时间几乎与 Julia 的相同。
然而,如果你要执行的计算有点复杂或定制,并且没有被优化的 numpy 函数覆盖,那么在速度方面你就不走运了。这是因为您必须依靠标准 Python 来填补空白,这导致了我们在图 2 中看到的“复杂”函数的巨大差异。
结论
在回答方面:Julia 真的比 Python 和 Numpy 快吗?
嗯,是的,在某些情况下,差距还很大。
和往常一样,重要的是要看到这些结果的本来面目,一些特定函数的小比较。即便如此,结果仍然是真实和相关的。Julia 是一种快速的语言,虽然我们在本文中没有过多地涉及它,但它实际上也很容易使用。
如果你想更全面地了解 Julia 与其他语言相比有多快,你可以看看 Julia 网站上的通用基准:
https://julialang.org/benchmarks/
同样的结果,它独树一帜,尤其是在数据科学领域。除非你用 c 写所有的代码。
最后一个问题…

图片来自皮克斯拜
如果 Julia 那么优秀,为什么没有 Python/Pandas/NumPy/R 那样的牵引力和认可度?
我觉得这个问题的答案主要是时间。它只是存在的时间还不够长,但现实是 Julia 正在崛起,在某个时候(至少在我看来)它可能会接管数据科学领域。例如,它已经被微软、谷歌、亚马逊、英特尔、IBM 和 Nasa 等公司使用。
Python 和 R 是现阶段的行业标准,这将需要很大的动力来改变,不管这个新贵有多好。
另一个因素是学习资源的可用性。同样,由于时间的原因,以及大量的人使用 Python 和 R 进行数据科学研究,可供学习的资源非常丰富。而对于 Julia 来说,虽然有大量的文档,但它并不能真正竞争所有的学习资源(目前还不能!).
…但是如果你想冒险,我鼓励你给朱莉娅一个机会,看看你有什么想法。
如果你觉得这篇文章有趣或者有用,记得关注我,或者注册我的时事通讯来获取更多类似的内容。
如果你还没有,你也可以考虑订阅媒体。你的会员费不仅直接支持我,也支持你所阅读的其他作家。你还可以完全不受限制地访问媒体上的每个故事。
使用我的推荐链接注册会给我一点回扣,对你的会员资格没有影响,所以如果你选择这样做,谢谢你。
https://medium.com/@maclayton/membership
LDA 话题建模死了吗?
原文:https://towardsdatascience.com/is-lda-topic-modeling-dead-9543c18488fa

图片作者。
利用嵌入式主题模型克服 LDA 的缺点
2003 年的论文Latent Dirichlet Allocation将 LDA 确立为现在可能是最著名和最广泛使用的主题建模算法(Blei et al. 2003)。然而,尽管 LDA 无处不在且经久不衰,但经历过 LDA 的人都很熟悉它的局限性。除了它的不稳定性之外,LDA 需要更多的文本预处理来获得好的结果。即使抛开实现细节不谈,LDA 也有一个更普遍的问题,这个问题困扰着主题建模者,不管他们使用什么算法——缺乏评估他们模型的基础事实。
为主题模型建立基础事实的唯一可靠和一致的方法是让专家创建一个公共的语料库主题词汇表,然后让多个注释者将词汇表应用到文本——这是一个耗时且昂贵的过程。这种“手工”方法,在社会科学中已经实践了几十年,正是无监督主题建模试图解决的问题。
获得一个客观的标准来衡量和评估一个模型的实际限制无疑导致了许多 ML/AI 实践者忽略了主题建模而去做其他不那么麻烦的工作。然而,尽管有缺点,主题建模,特别是 LDA,已经被证明足够有用来保持它们的流行。一篇讨论这个问题的论文简明扼要地总结了主题建模在面临其缺点时的持续吸引力
…虽然不能保证一个“主题”将对应于一个可识别的主题或事件或话语,它们经常以其他方法无法做到的方式做到这一点 (Nguyen et al. 2020) (着重部分由作者标明)。
对于这些作者和许多其他人来说,主题建模已经被证明是“足够好”的,值得他们继续关注。
本文探讨了一种新的技术,使用语言嵌入的主题建模,有效地解决了使用 LDA 时遇到的两个最突出的问题。这种新方法在论文 BERTopic:用基于类的 TF-IDF 过程进行神经主题建模(Grootendorst 2022)中有详细描述。BERTopic 是一个从嵌入数据中产生主题模型的端到端工具。默认情况下,它利用 HDBSCAN 来识别语言嵌入中包含的主题。尽管̶̶h̶̶̶d̶̶̶b̶̶̶s̶̶̶c̶̶̶a̶̶̶n[umap(bertopic 中默认使用它来减少语言嵌入的维度)是随机的,并且会受到运行变化的影响,但根据我的经验,它比 LDA 产生更稳定和可预测的主题分组。其次,因为 BERTopic 主题模型不同于它们总结的嵌入数据,所以有可能评估特定的运行是否很好地表示了底层数据结构。这个特性有效地创建了一个逐模型的基本事实,可用于评估和调优。没有什么可以和 LDA 相比。
TL;速度三角形定位法(dead reckoning)
用 BERTopic,别用 LDA!LDA 是一个强大的主题建模工具,但是它的不稳定性是一个主要的,通常不被承认的绊脚石。BERTopic 没有不稳定的问题。重要的是,本文试图证明作为主题建模基础的单词嵌入可以有效地创建一个基础事实,在此基础上可以对给定的主题模型进行评估和调整。从实用的角度来看,BERTopic 也更容易使用,因为没有文本预处理,并且如下所示,比 LDA 的资源密集度低得多。
免责声明和链接
我与 BERTopic 项目没有官方关系(与 LDA 或 EnsembleLDA 也没有官方关系)。作为本文的配套,我创建了一个 Tableau 演示,它将允许读者交互式地探索为本文创建的模型。本文中使用的数据是公开许可的,可以在ka ggle(CC0 license)上找到。我将一些技术细节和注释放在了文章末尾的附录中。
不稳定的地面
反对 LDA 的案例沿着两条轴线展开。首先是算法固有的不稳定性。我以前写过关于 LDA 主题模型不稳定性和为给定 LDA 模型建立客观正确的目标主题数量时固有的困难。为本文生成的所有模型都是从同一个语料库中生成的。LDA 或 EnsembleLDA 在三种不同的配置中针对语料库运行。对每一对进行评估,以确定它们彼此的一致程度。
使用 Gensim LDA 实施的默认参数的第一次 LDA 运行产生了以下热图,该热图比较了每个模型主题的文档/主题分配:

不相关的主题模型。图片作者。
橙色单元格表示出现在第一个模型的主题 2 和第二个模型的主题 4 中的文档数量,并记录了 62%的重叠。这是本次运行中最相关的主题对。尽管每个模型都基于相同的数据和相同的参数,但这些模型代表了非常不同的主题集。然而,通过增加我们对问题的处理能力,有可能得到更好的结果。
EnsembleLDA ,通过将多个模型协调到单个模型实例中,明确处理 LDA 模型不稳定性问题。使用与上述相同的数据和设置,我们可以看到模型相关性的显著提高:

改进的模型相关性。图片作者。
然而,这种改善是有代价的。在 Colab+实例上运行时,前两次相关性差的运行只需要几秒钟就能生成。上述改进的运行每个模型花费了三个多小时。如果我们在这个问题上投入更多的资源,将每个模型的处理时间增加到 9 个小时以上,结果会继续改善:

图片作者。
但是,尽管最后一组的相关性更好,LDA 模型不稳定性的影响是显著的。在第一次运行中,选择任意数量的 12 个主题作为模型的参数。但是,随后的四个模型都是用 EnsembleLDA 生成的。EnsembleLDA 的一个引人注目的特点是,在无人监督的情况下,该算法将收敛于优化数量的主题。然而,尽管 EnsembleLDA 在选择主题数量方面做得很好,但我们必须注意,在示例中,对于有多少个主题仍然没有达成一致。在 EnsembleLDA 模型中,我们分别看到 8、11、10 和 9 个主题。因此,即使后来的模型已经显著减少了它们的文档/主题分配中的漂移,在这个数据集中的主题数量上仍然没有一致意见。
其他判断 LDA 模型正确性的方法呢?最常见的方法之一是使用基于 npmi 的一致性分数(Lau et .艾尔。2014).有许多不同的变体,这里我们用 c_v,一个常见的选择。前两个模型的结果分别是. 291 和. 295。这些分数比第二组模型的分数低,分别为 0.578 和 0.566。然而,尽管最后一组模型显示出其主题之间的一致性比倒数第二组有显著提高,但它们的得分. 551 和. 549 比第二组差。虽然基于 npmi 的指标在实验室中表现良好,但以我的经验来看,它们往往达不到自己的承诺。**
使用可视化来理解模型
在散点图中可视化文档提供了信息丰富的可视参考,有助于理解每个模型中三万个文档的分布。以下是两个最佳相关 EnsembleLDA 模型之一的 TSNE·2D 简化:

2D TSNE 对 LDA 输出的预测。鼓励读者通过互动版探索剧情。图片作者。
我们可以清楚地看到主题之间的空间分隔。这个可视化的交互版本允许用户放大并悬停在代表单个文档的每个点上。通过这种方式,可以了解文档的内容是如何以特定的方式将它们分类的。
BERTopic 替代方案
如果您不熟悉 BERTopic 的软件包,我推荐您阅读它的文档以及它的作者 Maarten Grootendorst 发表的文章和论文中的 BERTopic 的架构和方法。简而言之,BERTopic 使用 HDBSCAN(或任何其他您愿意使用的聚类机制)来确定语料库中的主题。然后,它使用 TF-IDF 的变体— c-TF-IDF,它不是查看单个文档来提取有意义的词汇,而是聚合整个主题的文档并从整个主题中提取有意义的词汇。虽然我认为 c-TF-IDF 本身是一个重要的贡献,但在本文中,我将重点关注通过对 BERT 句子嵌入进行 HDBSCAN 聚类的主题发现,并将其与上面的 LDA 模型进行比较,而不涉及 c-TF-IDF 词汇发现阶段。
在 BERTopic 中,模型不稳定的问题实际上是不存在的。下面是一个热图,比较了使用相同语料库和相同参数创建的两个不同模型的文档分配:

图片作者。
根据设计,HDBSCAN 不会尝试对低于阈值的文档进行分类,这些文档被分配到类别 1。此外,BERTopic 从文档总数的最大到最小重新编号所有主题,这说明了上面矩阵的对称对角线-这是 BERTopic 的纯粹美学特征。
值得注意的是,连续的 BERTopic 运行将在很大程度上产生相同数量的主题。虽然在文档分配方面,模型与模型之间存在一些偏差和变化,但与 LDA 相比,不稳定性程度是最小的。最后,这些模型是使用启用 GPU 的 Colab+帐户在几分钟内创建的,与需要数小时计算时间的资源密集型 LDA 实现相比,这是一个巨大的实际差异。
我的袖子里几乎没有任何东西
细心的读者会注意到,上面使用的 BERTopic 模型只产生了六个主题(除了-1“主题”)。在使用这个语料库时,我发现当 HDBSCAN 针对嵌入运行时,存在一个“自然”的分割,其中体育故事包含大约六分之一的数据,并分成五个主题领域(有趣的是组织为:足球/橄榄球/板球、赛车、高尔夫、网球、拳击、游泳/跑步/奥运会),而所有其余的被组织为一个非体育相关文档的超级集群。因此,我将数据集分为两部分,运动和非运动。当 BERT 运行这两个分段的语料库时,体育分段保留了与原始语料库中出现的相同的内部组织,将体育分成六个分段。然而,“非体育”这个以前只有一个类别的 blob 被分解成了十个独立的主题。我对 HDBSCAN 难以细分非体育超级集群的原因的假设是,当所有数据都在一个组中时,hdb scan 无法在划分体育主题的同时将较大的数据集细分为更精细的分组。当文集被分成两个独立的部分时,一切都水落石出了。
正因为如此,也因为 BERTopic 中还没有工具来执行这种操作,下面的主题分组是从两个不同的 BERTopic 模型中组合起来的。用于获得正确的 HDBSCAN 参数的工具和技术超出了本文的范围。
嵌在…中的基本事实。嵌入?
下面是 2D·TSNE 对伯特嵌入的投影,覆盖了来自伯特普的最终主题,删除了-1 文档:

图片作者。
在图像的左上方,巨大的粉红色星团主要是足球,还有橄榄球和板球。其他运动:赛车、高尔夫、网球、拳击和游泳/跑步/奥运会,位于紧接其下方和左侧的五个集群中。Tableau 表示允许用户自由地遍历数据集,并向下查看文档级别,以了解更多关于 BERTopic 如何分割该语料库的信息。
另一个细分展示了 BERTopic 在主题之间进行精细、有价值的区分的能力。这种能力似乎远远超出了 LDA 的能力范围。另一个在 BERTopic 模型中可以观察到的细微差别的例子是大的中央绿色星团和它左边的两个分离的绿色星团。这些都是一个主题及其主题词的一部分:
狗,动物,动物,狗,物种,就像,猫,动物园,时间
最左边的群集似乎大部分或全部是关于狗的,中间的是关于猫的,大的分组(从左到右按空间组织)是关于外来动物、野生动物、海洋生物的,然后在这个更大的群集的边缘有一个分组与考古问题和少量的生物科学文献有关。这种清晰的空间/语义组织可以在整个 BERTopic 模型中找到。
我们可以使用散点图来比较由 BERTopic 和 LDA 创建的两个不同的模型。首先,我们可以将 LDA 主题叠加到 BERTopic 模型上:

图片作者。
LDA 主题通常与从 BERT 嵌入外推的坐标位置有很好的相关性。换句话说,较少数量的 LDA 主题或多或少符合 BERTopic 生成的主题分组。使用相同的 BERT 到 LDA 映射的热图,我们可以看到相同的有序数据的另一个视图:

BERT 主题/文档分布在左下方,LDA 分布在上方。图片作者。
0 和 1 LDA 主题是与体育相关的故事。我们可以看到,LDA 将这些文档分为两个集群,BERTopic 分为 6 个集群。其他六个 BERTopic 主题大致对应于 LDA 主题,但是剩余的四个 BERTopic 主题集群与 LDA 主题没有明确的关系。
反向视图,将 BERT 主题投影到 LDA 坐标上,揭示了一个更混乱的视图:

图片作者。
在这种情况下,我们可以看到,虽然一些领域大体一致,但在有分歧的地方,事情是相当混乱的。例如,右边有两只“眼睛”的粉色区域是体育报道。两个模型都很好地选择了足球故事。然而,LDA 模型无法有效地区分不同种类的运动。
翻转的热图以不同的形式显示了混淆程度:

左下方是 LDA 主题,上方是 BERT 主题。图片作者。
只有三个 LDA 主题可以说与它们的 BERTopic 对等物有任何程度的关联。
那又怎样?
上面的两个模型,一个是由 BERTopic 创建的,另一个是由 EnsembleLDA 创建的,显然是不同的,很难找到比通过关于它们各自的文档/主题分配的协议更多的东西。我们应该从哪里来解释这种分歧呢?主题建模者一直在处理这种不确定性。由于没有客观测量的基础,建模者只能做出他们所能收集到的最明智的主观判断。
然而,我认为,基于嵌入的主题模型提供了一种替代诉诸纯粹的主观措施。有了上面的数据,我对散点图的分析充满信心,BERTopic 模型在主题聚类定义方面更加精确。此外,我无法找到利用 LDA 散点图来辨别底层数据组织的任何有意义的东西,或者可以做些什么来改进模型。虽然每个散点图都是该特定模型的一个很好的表示,但据我所知,它与文档本身的底层语义结构无关。一次又一次基于 BERT 的散点图揭示了关于文档的惊人的组织良好的和重要的信息,以合理化的空间关系表达。在 LDA 可视化中找不到任何可比较的东西。
希望这篇文章激发了人们对嵌入主题建模的兴趣,特别是 BERTopic。这里介绍的简单实用的事实是:BERTopic 不需要文本预处理;LDA 模型不稳定性的论证问题;和经验丰富的主题建模者的兴趣所在。
然而,除了这些实际考虑之外,还有一种直觉,即 BERTopic 为其主题模型使用的嵌入有效地为主题建模建立了迄今为止难以捉摸的基础事实。基于我迄今为止的探索,似乎有理由认为 BERT 嵌入比 LDA 为创建主题模型创造了更坚实的基础。
当我们查看 LDA 嵌入时,我们看到的是一个数学过程的结果,该数学过程只有直接的语料库可以操作和推断语义。每个 LDA 模型代表一个小的、封闭的关系世界。当使用嵌入时,我们将我们的数据连接到一个更大的信息体,这个信息体声称在某种程度上代表了语言本身。在我看来,当检查基于嵌入的模型数据时,这种更大的关系是可见的。
附录
这些例子使用的数据是随机选择的,一个更大的公开许可的数据集新闻文章 ( CC0 许可)的 3 万篇文章子集 ( CC0 许可)。
正如文章中提到的,我跳出了 BERTopic,获得了优化的 HDBSCAN 参数,以便与 BERTopic 一起使用,从而得到这里显示的输出。用于实现它们的方法是从 BERTopic 模型中提取嵌入(实际上是嵌入的 UMAP 缩减),然后运行一系列改变 min_sample_size 和 min_cluster_size HDBSCAN 参数的实验。根据确定的主题群的数量和离群值的数量-1,文档分配来判断输出。我在这个数据集上发现,在几十次运行随机选择的值时,会产生“自然”数量的主题。有了这些数据,话题的数量聚集在 3、7 左右,然后跃升到 50 多个。我选择了产生这些异常值最少的集群配置的参数,然后运行散点图。基于这些结果,我认为将语料库分成两部分并重新运行调优实验可能是有意义的。最终结果如上所示,并形成 Tableau 表示的数据。我希望将来能更详细地写下这个技巧,鼓励那些对细节感兴趣的人通过我个人资料中的 LinkedIn 帐户与我联系。
学术界已经认识到,缺乏基本事实是主题建模的一个障碍。要了解更多关于这个问题的信息,我建议:
Nguyen,d .,Liakata,m .,DeDeo,s .,Eisenstein,j .,Mimno,d .,Tromble,r .,和 Winters,J. (2020)。我们如何用文字做事:将文本作为社会和文化数据进行分析。人工智能前沿,3,62。
奥康纳,b,巴曼,d,&史密斯,N. A .(未注明)。社会科学的计算文本分析:模型假设和复杂性。2022 年 6 月 28 日检索,来自https://people . cs . umass . edu/~ wallach/workshop/nips 2011 CSS/papers/oconnor . pdf
人文学科中的主题建模:概述。(未注明)。检索于 2022 年 6 月 28 日,来自https://Smith . UMD . edu/news/topic-modeling-in-the-humanity-an-overview/
文献学
文中引用:
布莱博士,Ng,A. Y .,&乔丹,M. I. (2003 年)。潜在狄利克雷分配。机器学习研究杂志:JMLR 。https://www.jmlr.org/papers/volume3/blei03a/blei03a.pdf?ref=https://githubhelp.com
Grootendorst,M. (2022)。BERTopic:使用基于类的 TF-IDF 过程的神经主题建模。在arXiv【cs。CL] 。arXiv。http://arxiv.org/abs/2203.05794
刘,J. H .,纽曼博士和鲍德温博士(2014 年)。机器阅读茶叶:自动评估主题连贯性和主题模型质量。计算语言学协会欧洲分会第 14 届会议论文集,530–539。
MATLAB 还有相关性吗?
原文:https://towardsdatascience.com/is-matlab-still-relevant-cf5627f171b8
意见
Jupyter 笔记本现在是数据科学事实上的通用语言。MATLAB 怎么了?

我最后一次使用 MATLAB 是在 90 年代末。我正在完成我的工程学位,我模拟了一个 sigma delta 模数转换器。我记得在大学里同时使用 MATLAB 和 Simulink。那只是我在大学里无数次使用 MATLAB 中的最后一次。
我把它束之高阁,直到几年前,我在 Coursera 上跟随吴恩达的机器学习课程,再次发现了它。我主要使用 Numpy,最近开始重新探索 MATLAB。
虽然行业和媒体似乎已经将 Python、Pandas、Numpy 和它的伙伴生态系统宣传为【正确的选择】并且已经逐渐远离 MATLAB,但我想知道这是否是因为第一个生态系统比 MATLAB 更有用和更有生产力,或者对开源生态系统有一些偏见。
两种语言如何处理基本的线性代数
数据科学的一些领域,尤其是当你学习这个领域时,与直接处理矩阵直接相关。
线性代数是 ML 的关键组成部分,但不仅如此,在许多情况下,由于大量的数据,建议使用矩阵方法。因此,能够容易地操作矩阵是数据科学中的一个关键需求。不是每次,不是每个人,但它是一个相关的需要经常。
尽管我开始有一定的飞行时间使用 Jupyter 笔记本、Pandas 和 Numpy,但我仍然发现它们的 API 和语法不直观。当我使用 Numpy 试图翻译和可视化矩阵时,尤其会发生这种情况。
Numpy 库非常强大,我怀疑你用 MATLAB 能做的事情,有什么是你用 Numpy 做不到的。Numpy 还可以在幕后用 C 和 Fortran 来丰富那些不能用 Numpy 实现的任务。但是,Numpy 仍然是一个添加到通用高级语言中的库,无论我花多少时间使用 Numpy,我仍然发现它有时不直观。
MATLAB 是一种矩阵操作语言(它自己的名字是 Mat 的缩写),它的语法更加直观,也更接近它所代表的东西:一种设计用于线性代数的语言。
对我来说,这是一个重要的观点,但这一点似乎与市场不太相关,因为我从未读到过任何人做出这一观察,但我确信我不是唯一注意到这一点的人。
交互式笔记本的作用
围绕数据科学的研究是一个迭代过程。工作流不同于软件开发中使用的工作流。总是有输入数据、算法或数学方法以及交互式图表和可视化。在数据科学中,任何其他要求要么大大减少,要么可以完全忽略。
一个显著的区别是软件方法也是过程化编程。尽管许多数据科学家在写笔记本时采用了面向对象的技术,但在我看来,这是他们年龄的反映(年轻人不知道什么时候一切都是过程化的),而不是实际的需要(当你有一个相当线性的执行线程时,你为什么想要对象?).
上述场景使笔记本成为完美的工具。这是一个线性的过程执行,您可以在特定的代码块上动态地进行迭代和更改。开发不复杂,整体结构简单。它有局限性,但总的来说,它们是一个简单而有用的工具。
这种交互式环境来自计算机时代的早期。事实上,MATLAB 一直是交互式的,甚至像 1968 年开发的 Macsyma 这样的程序也已经是交互式的了。Mathematica 也在 1988 年引入了 Notebook 的概念,所以如果有人认为交互开发过程是现代 Python 的事情,那就不是了。
然而,这种视觉编码块的方法在 Jupyter 笔记本中不知何故更独特或更好地实现,并且它是有用和多产的。这显然是它成功的原因之一。MATLAB 现在确实有一个类似的环境,名为 Live Editor ,但它直到 2016 年才拥有该功能。在此之前,它只是程序和交互式命令行。
我目前正在探索这个笔记本的 MATLAB 版本(正如我提到的,它被称为 Live Editor ),我发现它是一个非常有用的工具。
我认为笔记本的缺乏是 MATLAB 减少和 Python 生态系统采用 Jupyter 笔记本的一个原因。我现在无法想象没有他们在这个领域的任何研究或探索,我想在这个领域工作的许多人也有同感。
数据帧抽象
熊猫数据框是 Jupyter 笔记本的另一个“大”东西。如果可能的话,我倾向于不使用它们,因为我要处理大量的数据,而 Pandas 不像 Numpy 那样高效。
有些人将它们用作增广矩阵,但你只能用有限的数据量做到这一点。
熊猫是以表格形式处理异构数据类型的一种非常简化的方式。我特别把它们作为一个编排表来记录定量金融的分析结果,而实际的市场数据总是一个单独的 Numpy ndarrays 数据层。这使您在处理粒度数据时速度更快(在 Numpy 中),在记录和跟踪汇总结果时也更灵活(在 panasus 中)。我觉得这种两级方法很方便,到目前为止,我还没有找到更好的安排。不过,这可能只适用于某些情况。
MATLAB 可以被认为等同于 Numpy,但缺少等同的熊猫数据框架。现在它有了表格和时间表,这可以被视为熊猫的等效版本。
虽然在 MATLAB 论坛上讨论了很多关于表格和时间表性能的警告,但我认为这对熊猫数据框架也适用。表格和时间表看起来很棒,特别是与时间戳和时区处理相关的功能,现在在 MATLAB 中比在其他任何地方都好得多——我有这种特殊的需要,所以有时我会关注它。
虽然有时熊猫可能是必须的,但是更仔细地使用普通数组也可以得到等同于熊猫数据框架的结构。使用为名称分配特定矩阵列索引的单独变量来命名列并不罕见。你可以在 MATLAB 矩阵和 Numpy 数组中实现。这样你就有了充分的表现。
这似乎很奇怪,也很原始,但在数据科学的背景下,一切都需要有良好的结构,这就构成了一个不那么困难的问题。Numpy 数组也有自定义的数据类型定义,到目前为止我还没有在 MATLAB 中看到过(也许这个特性就在那里,我也不知道)。
ML 和 AI 生态系统的影响
ML 和 AI 现在处理了大量的注意力,所以每种环境处理得多好或多容易是非常相关的。
可以整合到 Jupyter 笔记本电脑中的最先进的库和框架数量巨大。这可能是一个具有更大灵活性和更多选项的环境,所有这些底层库和框架都是低级编码和高度优化的,甚至对于 GPU 和并行部署也是如此。不过,它与 Python 的集成有点人为,因为 Python 是一种通用编程语言,而不是矩阵语言。我怀疑,随着一个人获得更多的屏幕时间,这将成为一个小问题。
MATLAB 结合了几个工具箱来处理最大似然和人工智能。我仍在探索这些(可能是另一篇文章的素材),但 MATLAB 已经落后了,无法及时为 Jupyter 笔记本中丰富的生态系统提供支持。
在 MATLAB 中实现简单的算法和数值方法比在 Python 中做同样的事情要容易得多,在 Python 中你通常依赖于现成的库。可能正是这种“生产就绪”的生态系统提供了更大的用户基础。通常情况下,用户会对部署神经网络感兴趣,要么是因为他们已经通过了理解其工作方式的底层机制的阶段,要么是因为他们对其内部的更肤浅的知识感到满意。
关于成本的明显问题
Jupyter 笔记本和整个生态系统是开源和免费的,而 MATLAB 是一个商业产品。所以在这里,你是在比较一个相当昂贵的产品(MATLAB 本身并不昂贵,但取决于需要多少许可证和工具箱,其成本可能会显著增加)和一个开源免费的生态系统。
这一点很大程度上取决于目标和场景,值得讨论。
当 MATLAB 被用作学习工具或个人工具时,它相当便宜。
在写这篇文章的时候,一个捆绑的学生版 MATLAB、Simulink 和 10 个最常用的工具箱的价格大约是 75 美元,每个额外的工具箱不到 10 美元。考虑到软件的复杂性和目标受众,这几乎是一笔象征性的费用。如果你注册了任何学位课程,拥有 MATLAB 来学习和探索几乎是免费的。大学也可能会通过他们的学术许可来提供对它的访问。
对于个人非商业使用成本较大,MATLAB 本身并不昂贵,但工具箱会使最终的账单有点贵,但你仍然会发现 300-500 美元左右的价格是可以承受的,这取决于你想要添加的工具箱的数量。如果你需要更多的工具箱,价格就会上涨。它不像学生版,但价格仍然很合理,个人使用也负担得起。
真正的东西——在行业中使用 MATLAB 可以表示相关的成本或不相关的成本,这取决于具体的需求(有多少许可证和多少工具箱)。成本显然要大得多,但与此同时,如果你在商业上使用 MATLAB,是因为你处于高附加值行业(否则你为什么要使用它),与劳动力相比,成本可能仍然较低——你的工资、合同费率或服务费。这里 MATLAB 本身相当便宜(大约 2000 美元),但是没有工具箱的 MATLAB 可能不再有用。
市场怎么说
很难找到人谈论 MATLAB,它不再是一种趋势,它不酷,而且要花钱(相对于免费开源)。所以它看起来像是死了。甚至在 MATLAB 网站上,有一篇谈论 Python 和 MATLAB 的文章,其中解释了它最好与 Python 合作(这听起来不错,最终它只是另一个要使用的工具)。但是在厂商的网站上发现这样的声明让我很惊讶。
但是,尽管人们可能认为 MATLAB 成为了一种传统工具,Mathworks 的财务报表却讲述了另一段历史。时至今日,MATLAB 是一家健康的公司,在全球范围内雇佣了约 5000 名员工,拥有庞大的客户群,在过去的几年里收入一直超过 1B 元。这种情况随时都可能改变,但是从今天开始,这是一个很好的暗示,表明人们仍然在为 MATLAB 花钱。
判决
我怀疑 MATLAB 是另一个商业上成熟的产品,被广泛使用但不再流行,它是有用的,有它的位置,但它没有在媒体上获得太多的关注,并专注于机构和大客户。我个人认为它至少在我的知识阶段是有用的,因为它使我更容易处理我处理的数据类型,并使底层数学与 ML 和 a I 的关系可视化。
一旦我进入更成熟的知识阶段,我可能会重视其他方面,如能够部署模型或拥有更多现成的库,这种情况可能会改变。
我确信,就像任何其他技术一样,它会有支持者和反对者,但是对我来说,到今天,它似乎还没有死。Mathworks financials 将讲述一段不同的历史。
对我来说,这是一个值得探索的工具,我还不知道会用到什么程度。
我发现对 MATLAB 有用的资源
除了已经提到的评估 MATLAB 的低成本选项(学生许可证和家庭许可证),还有 30 天的试用期,如果我理解正确的话,当你创建一个帐户时,Mathworks 提供的信息 MATLAB 允许你每月 20 小时访问他们的在线云 MATLAB 版本。
我在 YouTube 上浏览了几个教程,也读了几本书。作为介绍/刷新材料,我强烈推荐官方的 MATLAB Onramp Course 。它是交互式的,在内容和互动性上都制作得非常好。浏览一遍需要 2 个小时,还有另外的关于 ML 和 AI 的课程,我打算接下来完成。
补遗
除了已经提到的非商业许可,有兴趣在商业上使用 MATLAB 的小企业应该知道,可以以非常有竞争力的大约 1500 美元的启动许可价格获得 MATLAB。该许可证仅对满足以下要求的初创公司有效:在过去 5 年内成立,少于 15 名工程师,收入少于 100 万美元。这些限制似乎相当合理,而且价格使得 MATLAB 对初创公司几乎是免费的。
—
放弃
我的观点是我自己的,不代表我的任何客户或雇主(过去或现在)的任何观点,也不与任何与我相关的特定专业活动(过去或现在)相关联。
我与这里提到的商业产品背后的任何公司都没有任何直接或间接的关系。
MATLAB 和 Simulink 是 MathWorks 的注册商标。
Mathematica 是 Wolfram 的注册商标。
此处提供的信息不代表任何购买建议或产品基准或详细比较。任何商业产品的成本和信息都是近似值,并不意味着准确或完整,有关功能、价格或其他规格的详细信息,读者可向不同产品的供应商咨询。
ML 可解释性是错误的目标吗?
原文:https://towardsdatascience.com/is-ml-explainability-the-wrong-goal-d2e94e1f4bf2
在高风险的应用中,可解释性甚至可能适得其反

照片由 Yosef Futsum 在 Unsplash 上拍摄
在他 2006 年的经典作品https://en.wikipedia.org/wiki/The_Shock_of_the_Old中,大卫·艾顿认为历史学家对技术史的理解过于受发明的支配。我们理所当然地怀念和钦佩伟大的发明家和科学家。事实上,David Edgerton 详细描述道,技术的采用和使用通常和发明本身一样重要,如果不是更重要的话。技术投入使用的方式在历史上有很大影响。在这种背景下,ML 的可解释性和可解释性成为非常重要和有争议的概念就不足为奇了,有时会误入滥用的口号中。ML 是一项关键技术,目前正被集成到基础设施和决策过程的关键部分,因此采用的方式无疑是重要的。具体来说,一个已部署的 ML 系统可解释或可说明的程度决定性地影响了人在该系统操作中的角色。
可解释性对可解释性
“可解释性”是指用户/接收者证明人工智能模型所做预测的能力。这通常是一种用于深入了解复杂模型的技术。例如,由于所用算法的复杂性,人类可能无法理解发生在数据上的转换(尽管他们会理解该过程在高层次上是如何工作的)。在这种情况下,可解释性技术提供了一些为什么做出复杂预测的建议。可解释性指的是他们从因果角度解释为什么做出预测的能力。
从这个意义上来说,可解释性是可解释性的更强版本(对模型输出的更彻底的基于因果关系的解释)。)可解释性通常被用来证明黑盒模型所做的预测是合理的,而黑盒模型是不可解释的。例如,通过置换输入或用代理模型拟合黑盒模型的预测,我们也许可以更好地解释预测过程中发生的事情,但不能从因果上证明为什么会做出决策。
有‘可解释性’就够了吗?
然而,有些模型可能永远无法解释,尤其是深度学习(DL)模型。这是因为对于这些模型,输入通过训练过程被不可识别地转换。DL 的核心原则之一是‘表示学习’,这意味着模型将它接收到的输入迭代地转换成新的表示(当输入通过神经网络的连续层时)。这种转换旨在最大化数据中的信号,为算法提供更大的牵引力,以进行准确预测。换句话说,这个输入转换过程允许机器在输入上获得更多购买,同时限制了人类分析师理解相同输入的能力。这种权衡是神经网络所固有的,也是这套强大的模型存在问题的原因之一。这些是附加了特别解释工具的黑盒模型(例如,计算机视觉中的显著性图,表格数据的 SHAP 值等)。)以抵消人类固有的无法理解这些转换输入的能力。
事实上,已经表明,一些流行的用于证明深度学习模型预测的可解释性技术是不可靠的。显著图是理解卷积神经网络中预测的常用方法,旨在揭示哪些图像像素在进行预测时最重要。然而,已经证明这些方法并不总是能够识别用于分类的图像的关键区域,这让我们质疑它们的实用性。一个可能不正确的可解释性方法有什么用?事实上,这可能会给用户一种错误的自信感。
我们如何定义“高风险”?
因此,一个关键的问题是,在人工智能系统实现之前,可解释性何时是必要的先决条件。Yoshia Bengio 对“系统 1”和“系统 2”的有影响力的区分可能有助于理解仍然使用黑盒和可解释性的辩护。他认为,系统 1 DL(构成“快速”感知式思维)已经由 ML 系统实现,如计算机视觉。然而,系统 2 DL(“慢”逻辑推理,可能涉及训练集数据分布之外的概括)还没有实现。本吉奥本人并没有提出这个论点,但是基于这种想法,有些人可能会认为系统 1 DL 不需要可解释性。我们大多数人都无法向朋友解释为什么我们会看到某个物体,或者为什么我们能够以某种方式闻到某样东西。
然而,在系统 1 的实现中(用大卫·埃哲顿的话来说,这是技术变革的创新部分),应用程序中 DL 的感知能力有时会取代人类的推理和逻辑,即使模型本身并不执行逻辑或推理。例如,以胸部 x 光片作为输入并预测相应患者哪里患有急性疾病的计算机视觉模型正在取代放射科医师可能用于根据 x 光片进行诊断的推理。与此同时,像这样的应用程序可以通过排除模型以高置信度预测扫描正常的扫描,从而给放射科医生更多的时间来诊断棘手的问题,从而显著改善患者的结果。
这显然是一个使用黑盒模型做出高风险决策的例子。但是,其他一些实现更难分类。使用谷歌搜索是高风险决策的一个例子吗?网飞的建议是高风险决策吗?可能需要对“高风险”的含义进行严格的定义,以便就每个用例所需的可解释性水平达成共识。与此同时,我们应该非常小心地调用可解释的方法,特别是当它们给予模型预测背后的推理以信心的时候。
参考文献
1 C. Rudin,停止解释高风险决策的黑盒机器学习模型,转而使用可解释模型(2019) ,《自然机器智能》第 1 卷,206–215 页。
[2]萨波塔等人。al,‘深度学习显著图没有准确地突出医学图像解释的诊断相关区域’(2021),medRxiv。
我的模型真的更好吗?
原文:https://towardsdatascience.com/is-my-model-really-better-560e729f81d2
为什么理论上看起来不错的 ML 模型不能保证在生产中很好地工作

弗洛里安·施梅兹在 Unsplash 上的照片
如今,一篇典型的 ML 研究论文是这样写的:
我们提出了一个新的模型架构 X。我们证明了 X 优于 SOTA Y %。我们的结论是 X 比现在的 SOTA 更好。我们的代码可在线获取。
这也是学术研究通常结束的地方。然而,从生产的角度来看,这远远不够。不能保证一个在纸面上看起来不错的模型实际上会成为一个好的生产模型。
在本帖中,我们将深入探讨在构建模型时所面临的额外挑战,不仅是为了研究,也是为了生产。我们将学习:
- 为什么离线性能不能保证在线性能,
- 为什么所有的错误都不一样,
- 为什么,除了对性能建模,延迟和可解释性也很重要
- 为什么你不一定要相信 ML 排行榜?
让我们开始吧。
离线性能不能保证在线性能
ML research 专注于的离线性能:该模型在静态的历史测试集上表现如何?另一方面,在 ML 生产中,我们关心的是在线性能:一旦模型被部署并对真实世界中的真实用户采取行动,我们的业务表现如何?
举个例子,
- 在欺诈检测中,合适的离线指标可以是 ROC-AUC,合适的在线指标可以是错过的欺诈交易的退款损失。
- 在搜索排名中,合适的离线度量是 NDGC,合适的在线度量可以是点击率。
- 在广告排名中,一个合适的离线指标也是 NDGC,但我们可能希望衡量在线广告总收入。
几个工业 ML 研究团队,如 Booking.com 的网飞和已经发现,线下模型性能的改善并不能保证模型在网上确实更好。一些线下表现较好的机型,线上表现甚至更差。Booking.com 研究报告中的线下和线上表现散点图显示两者完全没有相关性:

更好的离线性能(x 轴)并不能保证更好的在线性能(y 轴)。来源:Bernardi 等人,KDD 2019 ( 链接)
为什么更好的离线性能不能保证更好的在线性能?
原因之一是离线指标只是我们实际想要优化的业务指标的一个代理。这两者可能有关联,但这种关联并不完美。如果一个模型过度适应代理度量,那么它可能会偏离真正感兴趣的度量。这对于深度神经网络可能特别成问题,因为它们有大量的自由参数。网飞论文的作者警告说:
“如果深度学习模型被指定解决错误的问题,它会比功能较弱的模型更准确地解决问题。”
提高代理指标会有所帮助。例如,来自 YouTube 的研究人员发现,优化观看时间的模型比优化点击更有效,因为优化点击的模型最终会偏向于对用户几乎没有价值的点击诱饵视频。
错误不是错误
代理指标的另一个问题是,我们做了一个隐含的假设,即产生错误的人群之间没有质的区别。然而,这个假设并不总是成立的。错误并不都一样。
例如,在电子商务欺诈检测中,产品速度至关重要。速度更高的产品(如数字视频游戏)的假阴性更有影响力,因为不良行为者可以在短时间内造成巨大的破坏。因此,一个整体错误较少但高速产品错误较多的模型可能会导致更多的坏账,从而导致更糟糕的业务表现。
另一个领域,其中的错误已被证明有质的差异是广告排名。来自微软的研究人员发现,与高概率错误相比,低概率错误对商业利益指标广告收入有更大的影响。这是因为向用户展示一个不相关的广告比忽略一个相关的广告要糟糕得多:在最糟糕的情况下,用户可能会恼怒到离开。
延迟很重要
ML 研究论文很少讨论潜伏期。毕竟测试集是固定的,我们只需要评估一次就可以在论文中报出一个数字。
然而,在生产中,模型需要作为每天被数百万甚至数十亿用户使用的服务的一部分来运行。面向用户的应用程序中的一个关键指标是单请求延迟,这是单个用户请求从服务器收到响应所花费的时间。
模型的延迟有多重要?在一项实验中,Booking.com 大学的研究人员在他们的服务中引入了合成延迟,以便找到答案。结果呢?从统计数据来看,延迟与用户转化率之间存在显著的负相关关系。延迟增加 30%会损失大约 0.5 个百分点的转化率。“我们业务的相关成本”,研究人员在他们的论文中写道。
作者报告说,基于这一发现,Booking.com 优化了他们的 ML 系统的延迟,使用内部建立的简单线性模型,具有最少的功能集。
可解释性很重要
纸上的模型和生产中的模型的区别在于,生产中的模型会采取影响真实用户的行动。误报和漏报会导致问题升级。如果发生这种情况,我们最好能解释为什么我们的模型会出错。因此,可解释性是另一个在生产中很重要的模型属性,但在学术论文中并不经常讨论。
一般来说,模型越复杂,就越难解释它的预测。对于一个简单的线性模型,模型权重本身编码了模型所关注的内容,并可以让我们了解为什么会出现某些错误。对于随机森林或增强树来说,这变得更加棘手,对于深度神经网络来说更是如此。像 SHAP 这样的工具可以帮助解释复杂模型的决策,但是增加了延迟。正如我们之前看到的,在面向用户的应用中,延迟非常重要。
如果可解释性和延迟是硬需求,那么简单的线性模型可能是生产的最佳选择,即使建模性能可能不如更复杂的模型。
看别处效果和 ML 排行榜
ML 研究中的一个常见做法是在同一个测试集上评估多个模型,以便比较它们的性能。这种做法的问题是,仅仅由于随机的机会,一些模型被认为比其他模型更好,即使它们彼此一样好。
换句话说,只要我们在测试集上尝试足够多的模型,我们迟早会找到一个比我们只是偶然试图“击败”的模型更好的模型。这也被称为 望向别处效应 。
通常,离线模型性能的差异越大,测试集越大,结果的统计意义就越大。确切的统计显著性可以用一个叫做多重假设检验的统计框架来计算。
使用这个框架,人工智能研究人员劳伦·奥克登-雷纳计算了一场关于医学图像分割问题的 Kaggle 竞赛中排行榜的统计显著性。结果呢?在给定数据量和分数差异的情况下,模型#1 和模型#192 之间的差异可以显示为具有统计学意义。等级 1-191 之间的所有型号?从统计学的角度来说,我们不能断定其中任何一个比另一个好!
回到题目中提出的问题:我的模型真的更好吗?可能不是,可能只是偶然看起来更好。
外卖:给 ML 从业者的一些建议
让我为你的下一个 ML 项目总结一些实用技巧:
- 对离线性能指标有所保留。将它们视为健康检查,而不是生产性能的保证。相反,依靠随机对照试验来评估你的模型在生产中的表现。
- 除了离线性能,还要测量模型的延迟。延迟与用户体验有明显的负相关性,除非模型可以通过大幅提高性能来抵消负面影响,否则您会希望避免增加延迟。如果可解释性和延迟是硬约束,那么简单的线性模型可能是最好的方法。
- 错误并不都一样。研究来自不同人群的不同类型的错误对您试图优化的业务指标的影响。具有更好 AUC 的模型可能会犯更少但更严重的错误,从而导致更差的整体性能。
- 不要盲目依赖 ML 竞赛排行榜上的顶级模特。可能只是运气好。
在你走之前…
享受这个内容?请留下一些掌声(你最多可以留下 50 个),这有助于我保持光明。在 Medium 上关注/订阅,这样你就不会错过我以后写的新帖。 成为中等会员 这样你就可以无限制的查看文章了。并在LinkedIn和/或Twitter上关注我!
公开演讲是数据科学家的克星吗?
原文:https://towardsdatascience.com/is-public-speaking-the-cryptonite-of-data-scientists-bb7dac5925d6
帮助我提高演讲技巧的三个关键课程

大约七年前,在我攻读硕士学位期间,我是被选中与东南水务合作的三名学生之一。交易是公司会给我他们电表的数据,然后我会用这些数据作为我论文分析的基础。但是有一个陷阱!我还必须准备一套我论文的见解,并向 10 位利益相关者现场展示。
尽管我现在意识到这是一个锻炼我的演讲技巧和提升我的简历的好机会,但当时我并不这么认为。这将是我在商业环境中的第一次演示,在我的脑海中,我几乎绝对肯定地知道,不仅我会非常紧张,而且他们可能还会问我一些问题,这些问题会显示我缺乏作为分析师的经验(我第一次尝试冒名顶替综合症)。
从那以后,我花了很多时间来提高我的演讲技巧。我在培训会议上向商业利益相关者介绍了 300 多次。最近,我还有机会在帝国理工学院的数据科学研究行业展示会和我公司的数据科学峰会上进行现场演示(每次大约有 40 名观众)。
这次旅行让我意识到,演讲在很大程度上是一项可教的技能。当然,一些分析师比其他人更有演讲天赋,但是一些步骤可以帮助即使是最差的演讲者(像我一样)随着时间的推移而提高。为了帮助现在正在经历类似旅程的有抱负的数据科学家,我在下面概述了三个关键经验,它们帮助我提高了我的演示技巧(软技能和技术技能)和获得了我作为数据科学家的第一份工作。
完美不是在没有什么可以增加的时候,而是在没有什么可以拿走的时候

演示就像在大海里寻找珍珠一样。你通常要在探索数据时打开许多牡蛎,才能找到隐藏的珍珠。我在职业生涯开始时最常犯的一个错误是在我的演示中包括所有我打开的牡蛎。这与 Antoine de Saint-Exupery 的建议完全相反。相反,我应该只留下珍珠,其余的都留下。
首先,向非技术人员介绍和解释这些步骤要复杂得多,这大大增加了任务的难度。但更重要的是,这完全没有必要。商业利益相关者不会认为不详细的陈述不专业或不合标准。事实上,现实中恰恰相反。你的演讲越有针对性,越简单,就越容易理解,相比之下,过于沉重的演讲会让你的听众在几分钟后就失去兴趣。
在您开始创建演示文稿之前,有三个关键方面需要关注,它们可以帮助您从您的走带设备中分离噪音和信号:
- 谁是你的受众,他们的技术熟练程度如何?是业务利益相关者还是数据科学家?他们是高管、高级、中级还是初级?
- 为什么听众应该关心你演讲主题?如果你想说服他们,或者更好的是,激发他们参加演示,你会如何在 1 分钟或不到 1 分钟的时间内解释你演示的主要思想或目标?
- 你会呈现什么?如果受众和主要目标已经明确,下一步就是相应地设计你的演示文稿的初始结构、章节和流程。这很可能会随着演示的进展而改变,但仍将基于最初的设计。一般来说,我的牌组通常有五组,很少超过九组。这与研究表明大多数成年人平均可以在短期记忆中储存 7 个项目(5 到 9 个)有联系。这个想法是由米勒(1956)提出的,他称之为神奇的数字 7。所以通常,我有五个部分,每个部分有两到三张幻灯片,每一张都有助于我的分析结论(解决为什么)。
虽然花时间清楚地定义谁、为什么和什么可能会让你觉得这是一个不必要的步骤,但实际上会节省你的时间。更重要的是,它将为您的观众和您的演示创建一个更简单、更有针对性的平台。我将用伟大的法国数学家布莱士·帕斯卡的话来结束这一节:
'如果我有更多的时间,我会写一封更短的信'
2)你的视觉效果应该是资产而不是负债

既然我们已经讨论了如何从概念上设计演示文稿,下一步就是幻灯片的内容了。每张幻灯片都有两个主要组成部分,即实际的视觉效果(表格或图表)和对其中发现的见解的简短总结。
表格
在现场演示中使用表格通常是一个坏主意,因为它与我们的语言系统相互作用,这意味着我们必须阅读它们才能理解它们。这并不理想,因为在你演讲的时候,你希望你的听众把注意力集中在你所说的内容上,而不是试图去解读表格。在大多数情况下,您最好将数据可视化,或者将核心数字保留在幻灯片中,并在附录中添加完整的表格。这不适用于仪表板,因为它们的目标不同于 30 分钟的演示。
如果你决定使用桌子,记住你要让设计淡出背景,让珍珠占据中心舞台。这主要取决于你对边框、格式和字体的选择。

作者图片
图表
因为它们与我们的视觉系统相互作用,图表会比表格更快更容易地表达观点。然而,如果过于复杂,它们也可能是灾难性的。你的图表的唯一目的应该是帮助观众理解你想要传达的观点。另一方面,假设图很复杂,需要更多的努力才能理解。在这种情况下,最有可能的结果是你的观众会决定他们不想在它上面投入更多的时间,并在精神上检查(这是可以理解的)。
- 为图表使用标题
- 在 x 轴和 y 轴上使用标题,但是用浅色突出它们
- 你的 y 轴应该从 0 开始,以避免错误的视觉洞察
- 避免使用 3d 图表,因为它们会扭曲视觉解释,导致弊大于利
- 使用元素对齐并保留空白来突出珍珠
- 删除网格线
- 使用图例标注数据
- 使用颜色和形状来突出洞察力
- 选择最能展示你洞察力的图表

作者图片
总结
摘要通常位于幻灯片的顶部。这应该是不言自明的,这样观众就能理解这些见解以及它们如何有助于你的演示的主要目标。这也可以帮助你决定你的幻灯片的有效性。如果没有清晰的摘要或者没有提供任何增量信息,您可以考虑更改甚至删除幻灯片。
假装它,直到你成功了

由 Jonny Kennaugh 在 Unsplash 上拍摄的照片
这是演讲和公开演讲的标准语录,也是我职业生涯初期最讨厌的一句话。但这可能是给我的最好的建议。和其他事情一样,提高的唯一方法就是练习。
类似于你第一次骑自行车,你对第一次尝试不感兴趣,也不会因为失败而感到尴尬,因为你从来没有尝试过。相反,你首先使用辅助轮骑自行车,一旦你感到自信,你就开始不用辅助轮骑自行车,并在几次摔倒后坚持下去。
同样的原则也适用于演讲。你应该从小的演示开始,让自己感觉更自信,然后逐渐走向更多的观众。你也不应该害怕向别人寻求帮助。在我职业生涯的初期,我会在实际演示之前给自己演示十多次,甚至到今天,我仍然会做几次模拟演示,以确保我对流程和结构有信心。有时,我也会向我的同事做演示,这样做的好处是可以从那些没有花 10 多个小时做演示的人那里获得新的视角。这是测试你的牌组是否有足够的质量,以及它的结构和珍珠是否能被除你之外的其他人清楚地理解的最好方法。
结束语
就像好的演示一样,我想以上述三条经验的快速总结来结束这篇文章。我希望他们能像帮助我一样帮助你。
- 你的陈述应该简单明了,围绕你的目标和听众来组织(清楚地定义是谁,为什么和什么)
- 在幻灯片中使用视觉效果和摘要,使观点更容易理解。任何不利于这个方向的元素都应该被移除或改变
- 公开演讲可能令人生畏,但是通过练习和循序渐进,即使是最困难的演讲者也能变得熟练
保持联系
如果你喜欢阅读这篇文章,并想了解更多,别忘了 订阅 ,让我的故事直接发送到你的收件箱。
在下面的链接中,您还可以找到一个免费的 PDF 演示,介绍如何使用 r。
https://www.aspiringdatascientist.net/community
QuickSight 发挥作用了吗?
原文:https://towardsdatascience.com/is-quicksight-doing-its-job-47258fea38a7
亚马逊数据可视化工具述评

Firmbee.com在 Unsplash 上拍照
亚马逊 QuickSight 在数据可视化领域相对较新,但由于其易用性、快速推出以及与 AWS 环境的集成,它已经获得了快速的牵引力。我个人花了几年时间使用它,并在一个启动环境中推出它,所以我可以分享我的想法。它经得起期待吗?
创建您的第一份报告
QuickSight 运行在 AWS 云上,易于设置和部署可能是它最近获得大量关注的主要原因,特别是对于已经使用 AWS 堆栈的客户。
作为管理员或报告作者,您可以推出该工具,并在数小时内创建您的第一份报告。与需要分析师专业技能和领域知识的更传统的 BI 工具相比,这是快速走向市场的巨大优势。然而,在报告生成中有一些怪癖,管理员用户有时会陷入相对简单的任务或公式中。
数据处理和丰富能力
QuickSight 并没有把自己作为一个数据处理工具来销售(有很多其他的 AWS 组件可以做到这一点),这一点从它的第一次使用就可以清楚地看出。
它包含了添加计算字段的最常见的公式,例如解析日期和字符串,但是更高级的功能就有点有限了。例如,虽然技术上有窗口函数(用于计算数据分区上的公式),但这些函数不像在 SQL 中那样灵活,我经常发现自己不得不在 Postgres 中实现它们,因为这在 QuickSight 中是不可能的。
视觉能力和用户体验
QuickSight 提供了十几种常见的图表类型,如果您需要的话,它们非常好,设置和使用起来非常直观。然而,如果这不在你的期望范围内,你可能会发现自己受到了限制。
例如,不支持大多数统计图,如箱线图和回归图。
用户通常觉得使用图表很直观,但是当报告变大时,以用户友好的方式组织它们会变得很困难。
为每个报告添加描述和文档也不容易:目前只能在每个报告的顶部添加一两行文本。
导航和互动功能
图表可以使用所谓的“动作”通过参数相互链接。该功能总体上非常强大,允许动态加载图表以深入或扩展某些选择(例如,单击一个月的收入图表,它可以加载另一个包含细分的图表)。
问题是您需要配置一切,并且创建导航动作的过程非常麻烦,需要首先单独创建参数,然后经历许多配置步骤。
地理空间能力
地理空间能力是存在的,但相当基础。特别是如果你来自 Tableau 这样的工具,你可能会发现这很令人失望。
例如,不可能创建地理热图。
与外部数据源集成
QuickSight 允许与大量数据库和数据源集成。显然,这里的关键优势来自于与 AWS 组件的集成,例如,这允许极大地简化建立 VPN 连接或 SSH 隧道的过程。
文档和支持
文档是 AWS 级别的,意思是很棒。大量的实际例子,易于浏览。如果您有 AWS 的支持包,它还会包括 QuickSight,这在导航该工具的更高级功能时有时会很有用。
可扩展性和性能
作为亚马逊的产品,QuickSight 作为一等公民,在构建时就考虑到了可扩展性,这并不奇怪。
在内存消耗方面,有两种选择:一种是通过动态执行 SQL 直接收集数据(在这种情况下,将计算能力转移到数据源),或者将数据加载到 QuickSight 内存(SPICE 内存)。
这通常是首选选项,因为它允许报告运行得非常快,因为数据是预先加载到内存中的。
定价
低价位是 QuicksSight 的主要优势,也是它如此受欢迎的原因之一。
只读最终用户访问权限的上限为每个用户每月 5 美元,对于拥有大量用户的公司,还有公司扁平选项。
价格方面的一个巨大优势是用户是按比例收费的(每次会话),所以为可能不经常使用该工具的员工创建一个用户没有什么坏处,因为如果他们不使用它,他们不会被收费,或者如果他们偶尔使用它,收费很低(并且总是在每月 5 美元的上限内)。
管理员和报告作者的定价是每月 25 美元的固定价格。
存储也非常便宜,这是扩展 SPICE 内存使用的一个关键点,前面已经讨论过。
安全
该工具构建时考虑到了大量不同的用户群。安全设置可能非常精细,有时甚至是多余的。例如,没有自动向其他管理员用户授予新报告访问权限的选项,因为您每次都需要明确地这样做。同样,这可能是大型分布式组织的想法,即使管理员用户也不能完全控制系统,但在启动环境中这可能有点多余。
还有一个细粒度的安全选项来授予行和列级别的安全性,从而限制谁可以在同一个报告中看到什么。
行政和用户管理
这是一个工具还不成熟的领域。像创建和维护用户这样简单的任务变得不必要的痛苦。
例如,创建用户组(如“财务部”)和将用户分配给用户组的操作只能通过公共线路(AWS CLI)进行,而不能通过 web 界面进行。令人惊讶的是,对于一个为规模而构建的工具,亚马逊还没有优先考虑这些功能。
维修
一旦报告和代码库变大,以健壮的方式维护它就开始成为一个问题。
虽然在 QuickSight 中直接编写 SQL 来导入数据源在技术上是可行的,但这并不可取,因为没有代码历史或可重用性。
此外,报告可能会被误删除,并且没有程序性备份(尽管 AWS 支持人员已经明确表示,他们能够通过支持票证作为临时任务恢复已删除的资源)。
有可能在共享文件夹中组织报告,但是这看起来是一个非常基本的解决方案,并且不是那么用户友好。
报告分发
报告(仪表板)也可以以给定的频率通过电子邮件分发给最终用户。这很方便,但是这个特性仍然有一些限制。例如,当仪表板有多个选项卡时,只能在电子邮件正文中显示第一个选项卡,也不能将 pdf 或 excel 文件附加到报告中。
用法洞察
虽然可以说这不是最关键的特性之一,但对于推出数据可视化工具来监控其采用和使用的人来说,这是很有价值的。遗憾的是,此功能在默认情况下不可用,但是可以通过利用 QuickSight 生成的 AWS CloudWatch 日志进行专门开发来进行配置。
结论
总的来说,QuickSight 是一个很好的工具,特别是当您需要快速移动时,当您想要优先使用简单直观的工具时,以及当您需要扩展大量用户群时。当您有大量非结构化或原始数据,并且希望利用内部 QuickSight 内存时,它也能很好地工作。
当报告的复杂性很高并且可视化要求非常苛刻时,它就不太适合了。
QuickSight 仍然是一个相对年轻的工具,并且正在继续发展。它肯定有更大的潜力,人们应该期待亚马逊在这一领域的重大发展。
“小数据”是数据科学的下一个大事件吗?
原文:https://towardsdatascience.com/is-small-data-the-next-big-thing-in-data-science-9acc7f24907f
人工智能先驱吴恩达预测,未来十年将围绕以数据为中心的人工智能。如果我们有 50 个精心制作的样本,我们可能不需要几百万个嘈杂的样本。

我们可能正处于一个小数据时代的边缘【图片由 Daniel K Cheung 在 Unsplash 上拍摄】
在过去二十年左右的时间里,我们生活在一个“大数据”时代。随着存储容量和计算能力变得越来越便宜,我们可以存储和处理大量数据以产生新的见解。在谷歌、亚马逊和脸书的成功推动下,大规模数据分析取得了实质性突破,数据驱动决策成为许多企业的当务之急。
我们目睹了巨大的神经网络,有数百万个参数需要调整。大量实时处理的社交媒体信息流。从高频传感器和用户日志中提取数 Pb 的细粒度信息,存储在巨大的服务器群中。突破是丰富而令人振奋的。
毫无疑问,这样的大数据趋势将会持续。只要有更多的数据要收集,我们就会找到新的方法来利用它。例如,自然语言处理已经成熟,但视频分析仍然是一个绿色领域,仍然等待技术进步来推动发展。
尽管如此,硅谷之外的世界往往会被忽视。数以百万计的中小企业(和其他组织)正在处理需要全面数据解决方案的问题。这些组织只是想从他们的小数据集中提取有价值的见解——利用机器学习的最新技术——而不依赖于怪异的大数据集。对他们来说,这一时刻可能已经到来。
为了使潜在的应用更加具体,只需考虑以下几个例子:
- 成本会计:预测定制机器的成本
- 医疗保健:在 X 射线图像上识别肿瘤
- 制造:自动检测生产线上的缺陷
这些例子的相关性是显而易见的,因为数据可以发挥关键作用。然而,这些并不一定是数十亿数据点随时可用的任务,尤其是在考虑罕见的缺陷或疾病时。为了充分利用现代机器学习,需要一个不同的角度。

制造业是可能受益于数据驱动的缺陷检测的领域之一,然而相关缺陷实例的数量对于有效的机器学习来说往往太少【图片由 Mulyadi 在 Unsplash 上拍摄】
范式转变?
引进吴恩达。他创建了谷歌大脑,在斯坦福大学任教,共同创建了在线学习平台 Coursera(包括非常受欢迎的“机器学习”课程),并率先使用 GPU 进行机器学习,可以肯定地说他有一些可信度。当他发现数据科学中的一个新兴趋势时,倾听是值得的。
安德鲁认为——为了释放人工智能的全部潜力——是时候开始关注数据的质量,将这一运动命名为以数据为中心的人工智能。在过去的几年里,社区的焦点一直是以模型为中心,重点是设计、微调和改进适用于各种任务(文本挖掘、图像识别等)的算法。).
以模型为中心的研究非常富有成果,最终产生了许多高质量的架构。然而,要保持势头,仅仅设计和改进算法是不够的。对于真正的进展,模型输入的质量应该与转换的质量相匹配。
我们将更深入地重新审视以数据为中心的人工智能,但首先我们必须解决目前主导该领域的以模型为中心的人工智能。
以模型为中心的人工智能

在以模型为中心的人工智能中,数据被假设为给定的。重点是改进模型,试图从固定数据集获得最佳性能[图片由作者提供]
传统上,数据被认为是算法的给定输入。主要问题是什么机器学习算法从数据中榨取最多。我们需要梯度增强树还是神经网络?多少层,哪些激活函数,哪些梯度下降算法?过多的选项给确定合适的架构带来了许多挑战。大型数据集允许克服噪音和缺失数据。
吴恩达假设以模型为中心的人工智能现在已经达到了一个饱和点。许多悬而未决的问题已经解决,广泛评估了各种任务的架构。例如,谷歌的自然语言处理算法 BERT 已经在英语语言上进行了训练。对于另一种语言,我们可以使用 BERT 体系结构作为起点——在这个过程中进行调整和剪裁——而不是从头开始。
以模型为中心的 AI,通过才华和经验,给我们带来了很多。对于许多常见的问题,我们现在已经有了合适的算法,这些算法已经被经验证明能够很好地工作。这意味着我们可以对某些问题类别使用现有的模型,而不是为我们遇到的每个问题实例重新发明轮子。结合可用的工具,人们不再需要成为算法专家来部署行业就绪的人工智能。
显然,以模型为中心的人工智能不是一条死胡同——算法的进步将永远继续。然而,开源库和示例架构对解决人工智能问题大有帮助。就改进潜力而言,现在在数据方面比在模型方面有更多的收获。
以数据为中心的人工智能

在以数据为中心的人工智能中,模型或多或少是固定的。相反,重点是提高数据质量,旨在深入理解小数据集[图片由作者提供]
尽管每天都有令人眼花缭乱的数据产生,但这些数据的质量可能相当差(任何数据科学家都知道)。缺失数据、输入或测量错误、重复、不相关的预测因素——这些都使得训练模型变得困难。足够大的数据集可能会克服这样的障碍,然而一个既小又质量差的数据集是一个灾难。
此外,我们通常只对数据的特定子集感兴趣。一千万张健康肺的图像或者一堆非欺诈性交易对于手头的用例帮助不大。即使数据集乍一看足够大,我们也经常处理严重的 类不平衡 ,只有很少有意义的例子可以学习。
承认数据质量的重要性并不新奇——众所周知的是亚当垃圾输入=垃圾输出。数据清理通常是临时进行的,依赖于单个数据科学家的独创性。更糟糕的是,预先确定哪些数据属性(异常值、缺失值、转换等)并不清楚。)对模型性能的影响最大,导致令人沮丧的反复试验循环。**
相比之下,以数据为中心的人工智能传播一种系统化和系统化的方法** 来提高数据质量,针对对性能影响最大的数据段。通过识别显著特征、消除噪声、分析错误和一致地标记,训练的有效性可以显著提高。关键是推广和自动化这样的程序。**
到目前为止,重心一直在改进模型上,而不是改进数据本身。以数据为中心的人工智能旨在改变这一点。
向小数据的转变
系统地提高数据质量的概念是有道理的,但是具体来说,我们能期待什么样的发展呢?向“小型智能数据的转变是关键,关注高质量的数据和可解释的例子。
经过验证的架构只需做一些修改就可以重用,或多或少地修复模型。在这种情况下,数据本身成为感兴趣的对象。通过固定一个变量(模型),对另一个变量(数据)的分析就容易多了。然而,很难从大型数据集中找到意义。深入的人体分析需要小而全面的数据集。
以数据为中心的人工智能将需要文化上的重大转变。我们将花更多的时间对数据集进行标记和切片,而不是摆弄层和超参数。因为这些是我们大多数人不一定喜欢的任务,所以这种文化转变不可等闲视之,即使长期愿景包括单调乏味的清洁任务的自动化。
最后,以数据为中心的人工智能传播了一种关于提高数据质量的系统方法。可以区分两个主要方向:
- ****开发检测不一致性的工具。要真正扩展和推广数据改进,必须自动化检测和清理,摆脱耗时且容易出错的手动数据清理例程。至关重要的是,清洁操作应该是一致的和可解释的。
- ****利用领域知识。为了准确解释所传达的信息,需要专家仔细检查数据集。需要精确和准确的特征和阈值来充分利用数据,以及识别潜在的遗漏示例。
人类的可解释性是这些趋势的核心,促使人们转向小的、可解释的数据集。吴恩达声称,50 个优秀的例子可以同样好地训练一个 ML 算法,而不是数百万个嘈杂的例子。显然,这些例子的设计工作将是实质性的,每一个例子都会产生有意义的贡献。
实际上,想要的数据可能并不总是容易得到。为了扩充现有数据(基于特征分析,我们可以识别快速获胜),一个有希望的方向是 生成式 AI ,能够构建与现实无法区分的合成数据。基于例子和领域知识,我们可以精确地构造具有我们需要的属性的人工例子,例如,一种罕见的缺陷的图像或特定的股票市场跳跃。
向小数据的转变将对数据科学产生巨大影响。它为许多没有大量相关数据集的问题打开了大门。它允许生成高质量的人工数据集。它与已经获得牵引力的可解释的人工智能运动相一致。事实上,这将是该领域的一个根本性突破。
关键要点
随着过去几十年来机器学习算法的许多突破,似乎已经达到了一个饱和点。我们有大量的(开源)库和经过验证的架构来处理各种任务,因此模型可以在很大程度上保持固定。考虑到这一点,以数据为中心的人工智能可能是下一个突破,重点是在最重要的地方提高数据质量的系统方法。
当前的训练方法通常依赖于足够大的集合来克服噪声和缺失数据。然而,许多现实世界的问题只产生小数据集。如果我们通过仔细检查例子来精心制作代表性的输入,小集合可能足以训练高质量的模型。为了实现这一点,人类的专业知识和系统的改进方法对于实现可推广的进步都是至关重要的。
具体来说,数据科学的不久的将来可能需要重新关注一些活动,如(I)专家分析,(ii)一致标记,(iii)噪声去除,(iv)误差校正,(v)特征工程,以及(v) 人工数据生成。领域专业知识和人的可解释性将在小型数据集的深度评估中占据更重要的位置,但长期目标是提供系统化和自动化的解决方案来调查和提高数据质量。
以数据为中心的人工智能可能会在很大程度上改变许多数据科学家的日常任务。快速的胜利会积累竞争优势,现在看来,大多数胜利可以通过改进数据而不是算法来实现。
进一步阅读
**https://amplify.nabshow.com/articles/ic-smaller-smarter-data-needed-to-train-and-scale-ai/ https://www.forbes.com/sites/gilpress/2021/06/16/andrew-ng-launches-a-campaign-for-data-centric-ai/?sh=69763abc74f5 https://fortune.com/2022/06/21/andrew-ng-data-centric-ai/ https://mitsloan.mit.edu/ideas-made-to-matter/why-its-time-data-centric-artificial-intelligence https://spectrum.ieee.org/andrew-ng-data-centric-ai https://www.techopedia.com/what-is-data-centric-ai-and-why-do-we-need-it/2/34756 **
现代的数据仓库坏了吗?
原文:https://towardsdatascience.com/is-the-modern-data-warehouse-broken-1c9cbfddec3e
意见

现代数据仓库体系结构在许多层面上都产生了问题。图片由 Chad Sanderson 提供。
数据仓库是现代数据栈的基础,所以当我们在 LinkedIn 上看到护航队数据负责人 Chad Sanderson 宣称,“数据仓库崩溃了”时,它引起了我们的注意。
当然,Chad 指的不是这项技术,而是它是如何被使用的。
在他看来,数据质量和可用性问题源于传统的最佳实践,即将数据“转储”到仓库中,然后进行操作和转换以满足业务需求。这与 Snowflake 和 Databricks 等提供商为确保客户在存储和消费方面高效(换句话说,节省资金和资源)所做的努力并无出入。
不管你是否同意下面详述的 Chad 的方法,无可争议的是他的观点如何引起了大量的争论。
“一个阵营对我很生气,因为他们认为这不是什么新鲜事,需要长时间的手动流程和拥有 30 年经验的数据架构师。另一个阵营对我很生气,因为他们的现代数据堆栈根本不是这样建立的,也不是他们构建数据产品的方式,”Chad 说。
我将让您自己决定“不可变数据仓库”(或主动与被动 ETL)是否是您的数据团队的正确道路。
无论如何,我强烈支持推动我们的行业前进需要的不仅仅是对数据仓库和数据观察平台等技术的概述,而是关于如何部署它们的坦率讨论和独特视角。
我们让查德接手。
不可变数据仓库如何结合规模和可用性
来自查德·桑德森的观点
现代数据堆栈有许多排列,但数据仓库是一个基础组件。过于简单化:
- 数据通过被动管道提取(实际上只是 ETL 中的“E ”),然后转储到…
- 一个数据仓库,在那里数据被处理和存储,然后…
- 转换成数据消费者所需的格式,用于…
- 特定用途,如分析仪表板、机器学习模型或记录系统(如 Salesforce 或 Google Analytics)中的激活…
- 数据可观察性、治理、发现和编目等技术或流程在整个体系中运行。
在深入探讨这种方法的挑战和建议的替代方法之前,有必要探究一下我们是如何达到我们所定义的“现代数据堆栈”的。
我们是怎么到这里的?
在数据的早期,有了像比尔·恩门这样的先驱,最初的 ETL(提取、转换、加载)过程包括从数据源中提取,并在进入数据仓库之前进行转换。
许多企业至今仍以这种方式运营。对于数据质量至关重要的大公司来说,这一流程涉及一个手动的密集型治理框架,数据工程师和嵌入不同领域的数据架构师之间紧密耦合,以便快速利用数据获得运营洞察。
谷歌、脸书等科技巨头抛弃了这一过程,开始将几乎所有东西都转储到数据仓库中。对于快速成长的初创公司来说,逻辑组织数据的投资回报率没有这个更快、更具可扩展性的过程高。更不用说,加载(ELT 中的“L ”)已经变得更容易集成到云中。
与此同时,流行的转换工具使得在仓库中转换数据比以往任何时候都更容易。模块化代码和显著减少的运行时使 ETL 模型大大减少了痛苦…以至于流行的转换工具的使用从数据工程师扩展到了数据消费者,如数据科学家和分析师。
似乎我们已经找到了一个新的最佳实践,并且我们正在走向事实上的标准化。以至于提出一个替代方案会引起迅速而强烈的反应。
仓库中被动 ETL 或转换的挑战
一旦数据进入数据仓库,严重依赖于数据转换的体系结构和流程会出现几个问题。
第一个问题是数据消费者(分析师/数据科学家)和数据工程师之间的脱节,实际上是鸿沟。
项目经理和数据工程师将在分析师的上游建立管道,分析师的任务是回答来自内部利益相关者的某些业务问题。不可避免地,分析员会发现数据不能回答他们所有的问题,项目经理和数据工程师已经离开了。
当分析师的反应是直接进入仓库并编写一个脆弱的 600 行 SQL 查询来获得答案时,第二个挑战就出现了。或者,数据科学家可能会发现,他们构建模型的唯一方法是从生产表中提取数据,这些生产表作为服务的实现细节。
生产表中的数据不用于分析或机器学习。事实上,服务工程师经常明确声明不要依赖这些数据,因为这些数据随时都可能发生变化。然而,我们的数据科学家需要做他们的工作,所以他们无论如何都要做,当表被修改时,下游的一切都会中断。
第三个挑战是,当您的数据仓库成为垃圾场时,它就变成了数据垃圾场。
一项来自 Hadoop 时代的 Forrester 研究发现,企业中 60%到 73%的数据没有用于分析。T2 最近的一项希捷研究发现,企业 68%的可用数据未被使用。
因此,数据科学家和分析师花费太多时间在过度处理的生产代码堆中搜索上下文。作为数据工程师,除了数据质量,我们还需要强调数据可用性。
如果您的用户不能在您当前的数据仓库中可靠地找到并利用他们需要的东西,那还有什么意义呢?
另一种方法:引入不可变数据仓库
不可变数据仓库概念(也称为主动 ETL)认为,数据仓库应该是真实世界的数据表示,而不是随机查询、中断的管道和重复信息的混乱。
有五个核心支柱:
#1 制定业务计划,分配所有者。为了让企业真正从他们拥有的大量数据中获得价值,团队需要后退一步,在通过代码定义实体和事件以实现明确的分析目的之前,对他们的业务进行语义建模。这可能是一个从最关键的业务元素开始的迭代过程。
实体关系图(ERD)是基于现实世界的业务地图,而不是现在存在于数据仓库或生产数据库中的。它定义了关键实体、它们的关系(基数等),以及表明它们已经交互的真实世界的动作。每个实体和事件都有一个工程负责人。端到端的自动化沿袭可以帮助建立 ERD,并使其可操作。
#2 数据消费者预先定义他们的需求,然后签订合同。也许最有争议的租户是数据应该从业务需求中涌现出来,而不是从非结构化管道中渗透出来。数据分析师和科学家不是在你仓库满是灰尘的货架上搜寻是否有足够接近他们需要的数据集,除非数据消费者首先直接请求和定义,否则没有数据进入仓库。
如果没有业务问题、流程或驱动数据的问题,任何数据都不会进入数据仓库。一切都是为了要完成的任务而设计的。
这一过程必须设计得简单,因为数据需求总是在变化,不断增加的摩擦会威胁到采用。在康弗公司,实施新合同需要几分钟到几小时,而不是几天到几周。
接下来,是起草数据契约的时候了,这是业务和工程领导之间关于事件/实体的模式应该是什么以及该资产最有效最需要的数据的协议。例如,可能现有的 inboundCall 事件缺少 OrderID,这使得很难将电话呼叫与已完成的订单联系起来。
SLA、SLIs 和 SLO 是一种类型的数据契约,您可以将应用于这种变更管理和利益相关者联合的模型。
#3 活跃环境中的同行评审文档。同样,我们需要一个针对生产的代码(GitHub)或 UX (Figma)的同行评审过程,对于数据资产也应该有一个对等的过程。然而,对于这篇评论来说,正确的抽象层次不是代码,而是语义。
该审查过程应该与 GitHub 拉请求具有相同的结果——版本控制、相关方的签署等——都通过云处理。通过应用现代的、基于云的技术,我们可以加速旧的流程,使它们在发展最快的互联网业务中变得更加可行。
数据目录可以作为预数据仓库定义表面,但挑战在于没有胡萝卜和大棒让数据消费者保持元数据最新。对于一个使用 ELT 流程并完成模型的数据科学家来说,回去记录他们的工作的动机是什么?
#4 数据通过管道传输到合同中定义的预建模仓库。转换发生在消费层的上游(理想情况下在服务中)。然后,工程师在他们的服务中实现数据契约。数据通过管道进入数据仓库,理想情况下,建模元数据可以自动连接和分类。
#5 重点在于防止数据丢失以及确保数据的可观察性、完整性、可用性和生命周期管理。事务发件箱模式用于确保生产系统中的事件与数据仓库中的匹配,而日志和偏移处理模式(,我们在护航中广泛使用)用于防止数据丢失。这两个系统共同确保了数据的完全完整性,因此不可变数据仓库是整个业务中发生的事情的直接表示和真实来源。
数据质量和可用性需要两种不同的心态。数据质量在很大程度上是一个技术挑战。想想“后端”工程。另一方面,数据可用性是一个“前端”工程挑战,需要与创建卓越客户体验相同的技能。最后,一个不可变的数据仓库不适合进行 Pb 级的测量竞赛,也不适合拿出你的大数据统计数据。弃用和维护与资源调配一样重要。
这种方法利用技术的优势来实现两全其美。传统方法的治理和业务驱动方法与现代数据堆栈相关的速度和可扩展性。
不可变数据仓库的工作原理。像对待 API 一样对待数据。

不可变数据仓库的层。图片由 Chad Sanderson 提供
让我们从回顾围绕不可变数据仓库的整个堆栈开始。
1。描述层:与传统的仓库不同,描述层将业务逻辑移到服务层之上,将数据消费者放在驾驶座上。消费者能够提供他们的需求,而不需要技术技能,因为数据工程师是代码翻译者的关键需求。这些合同可以保存在一个数据目录中,甚至可以保存在一个通用文档存储库中。
2。数据仓库:数据仓库的主要功能是“数据展示”和底层计算层。
3。语义层:数据消费者构建数据产品,这些产品经过验证并与业务部门共享。语义层中的资产应该被定义、版本化、审查,然后通过 API 提供给应用层使用。
4。应用层:这是使用数据完成一些业务功能的地方,比如实验、机器学习或分析。
5。端到端支持:支持跨数据栈的数据操作的解决方案,比如数据可观察性、目录、测试、治理等等。理想的情况是,一旦数据到达仓库,就有完美的、预先建模的、高度可靠的数据,但是您仍然需要涵盖现实世界可能向您抛出的所有排列(并且在流程越界时有强制执行机制)。
不可变数据仓库本身是为流设计的——从流到批量数据比从批量数据到流更容易——因此由三种不同类型的 API 支持。

蒙特卡洛图像库。
- 语义事件 API: 这个 API 是针对语义现实世界服务水平事件的,这些事件是公司的核心构件,而不是来自前端应用的事件。例如,在车队的情况下,这可能是当一个货物被创建或解除搁置。来自真实世界的事件构建在服务代码中,而不是 SQL 查询中。
- CRUD Abstraction API: 数据消费者不需要看到所有的生产表,特别是当它们只是数据服务的实现细节时,他们使用这些数据服务来产生洞察力或制定决策。相反,当生产表中的数据资产的属性被更新时,API 包装或抽象层(例如,dbt)将公开对仓库中的数据消费者有意义的 CRUD 概念,例如,数据是否是新的或行容量是否在预期阈值内。
- 前端 API: 有很多工具已经可以处理前端事件定义和发射,比如 Snowplow、Mixpanel 和 Amplitude。也就是说,一些前端事件非常重要,团队需要能够使用长偏移管道来确保它们的交付和完整性。在某些情况下,前端事件对于机器学习工作流至关重要,而“足够近”的系统无法满足这一要求。
随着情况的变化(也许一个服务需要变成多个服务),或者如果数据科学家头脑中的模式不适合现实世界中发生的情况,还需要有一个位于仓库外部的映射层。
映射应该通过流式数据库在仓库的上游处理,或者在仓库本身中处理。在这一层,BI 工程师将工程中出现的东西与数据消费者需要的东西相匹配,这可以自动生成金博尔数据集市。
不可变数据仓库也面临挑战。以下是一些可能的解决方案。
我并不认为不可变的数据仓库是银弹。像任何方法一样,它也有优点和缺点,当然也不适合每个组织。
像数据网格和其他崇高的数据架构计划一样,不可变的数据仓库是一种理想状态,很少是现实。实现或者试图实现一个梦想是一个旅程,而不是目的地。
应该考虑和缓解的挑战有:
- 定义描述性图层的前期成本
- 处理没有明确所有权的实体
- 实施新方法,实现快速实验
虽然定义描述层是有成本的,但是可以通过软件大大加速,并通过优先考虑最重要的业务组件来迭代完成。
这需要一个包括数据工程师在内的协作设计工作,以防止数据质量责任在分布式数据消费者之间扩散。如果你第一次没有做对也没关系,这是一个反复的过程。
处理没有明确所有权的实体可能是一个棘手的治理问题(也是一个经常困扰数据网格支持者的问题)。数据团队通常无权在业务方面对这些问题进行分类。
如果有一个跨多个团队的核心业务概念,并且是由一个整体而不是微服务产生的,那么最好的方法是建立一个强大的审查系统,并有一个专门的团队随时准备进行更改。
数据工程师仍然可以在不限制工作流程的情况下进行试验并获得灵活性。实现这一点的一种方法是通过一个单独的暂存层。然而,来自这些暂存区的 API 数据不应该被下游或跨外部团队使用。
关键是,当你从实验转移到生产或者让边界团队可以访问时,它必须经过相同的审查过程。就像在软件工程中,你不能仅仅因为你想更快地前进,就在没有审查过程的情况下进行代码更改。
祝您在数据质量之旅中好运
现代数据堆栈有许多排列,作为一个行业,我们仍在经历一个实验阶段,以了解如何最好地铺设我们的数据基础架构。
显而易见的是,我们正在迅速走向一个未来,在这个未来,更多的任务关键型、面向外部的复杂产品将由数据仓库“驱动”。
无论选择哪种方法,这都要求我们作为数据专业人员提高我们的标准,加倍努力获得可靠、可扩展、可用的数据。数据质量必须是所有数据仓库的核心,不管是什么类型。
从我的角度来看,底线是:当你建立在一个庞大的、无定形的基础上时,东西会坏掉,很难找到。当你真的找到它时,很难弄清楚它到底是什么。
不管是不是不可改变的,也许是我们尝试新事物的时候了。
本文与 查德桑德森 合著。
对提高您的数据仓库的数据质量感兴趣吗?约个时间跟 蒙特卡洛团队 聊聊。
正态曲线好到不真实吗?
原文:https://towardsdatascience.com/is-the-normal-curve-too-good-to-be-true-c7cf2fd33997
你的数据是这样认为的。

作者图片
你还记得那天在统计学课上你听说了正态曲线吗?它是钟形的。它是对称的。它有一个公式。自然界中许多可以测量的东西都遵循这个形状——这是上帝曲线。
传统的推断统计学实际上是建立在这样的假设之上的,即通过足够多的实验(即样本),我们得到的分布将是正态的(是的,我现在省略了技术细节)。
从这个角度来看,坦率地说,我们也可以说正常曲线是便利的。最起码有时候会被当成常识。
…给定的人口通常是围绕一个统计标准来观察的——water house,2004 ( 来源 )
但是正态曲线好到不真实吗?说到推理测试,不幸的是似乎如此。根据过去半个世纪进行的研究,基于正态假设的传统方法在很多情况下都失败了。
在本文中,我将在Deepnote 中使用图表和模拟来展示依赖于常态的传统方法是如何让我们失败的。我们还将看看用 Python 实现的替代方法,它们更加强大和准确。将提供所有代码,以便您可以自己检查结果。
正态曲线的神话
实际上有一篇关于正态曲线流行的很棒的论文,作者是 Micceri,叫做 《独角兽》、《正态曲线》和其他不可思议的生物 (这是个标题!).论文检查了 440 个大样本分布,发现它们在统计上是非正态的。因此,米塞利质疑假设正态性的统计数据的稳健性。确实有其他的论文和书籍研究和评论正态曲线的存在( 1 、 2 、 3 )。
神圣的钟形曲线肯定已经从上到下裂开了。也许,像自由钟一样,它应该被铭记在某个地方,作为对更英雄的日子的纪念——欧内斯特,《费城询问报》。1974 年 11 月 10 日。( 来源 )
学生 t 分布
正态曲线有一个近亲叫做学生的 t 分布。我们可以很容易地用它来证明,我们在统计中被告知的假设,并不总是与现实相符。
t 也是钟形的,尾巴轻,对称。当样本量达到∞,t 分布接近正态分布。在许多统计学书籍的后面(当然还有网上),你可以找到基于各种场景的 T 的阈值。当我们进行 T 检验时,这些阈值帮助我们确定我们的结果是否具有统计显著性。
当然,我们对这些数字门槛的信心应该和潜在的假设一样坚定。也就是说,我们假设,

遵循一个分布。但是真的吗?让我们检查一下。
比较假设的和估计的 t 分布
灰色曲线是假设的 t 分布。蓝色曲线是我们在现实中得到的 t 分布。为了澄清,我使用计算机基于对数正态总体分布(5000 个样本;n=20)。正如您在这个场景中看到的,我们假设 T 遵循一个 t 分布并不成立。由此可以得出一个非常严肃的结论:当样本来自偏斜分布时,T 检验会导致假阳性率增加(I 型错误)。对于重尾或轻尾分布都是如此。你可以通过对比上图中的左尾巴来直观地理解这一点。蓝色曲线中间的 95%比灰色曲线宽得多。这意味着,相比之下,我们更有可能以惊呼“我的结果很重要”而告终,而实际上并不是( 4 , 5 )。
我们可以将 T-test 置于许多其他场景中,以确定它的表现如何。事实上,这个笔记本允许你有计划地调整人口分布的偏度和权重(异常值的比例)。我们可以将此作为估计 I 型错误率的模拟的基础。让我们来看看当总体具有重尾时 T 检验的表现,并且让我们将它与一个稳健的替代方法进行比较:百分位引导检验(不要担心,我很快将向您展示如何使用它!).
比较第一类错误率
在这种情况下,我将虚构的人口形状设置为对称的,就像一条正态曲线,但允许它有沉重的尾部(所谓的污染的正态)。从上面的图表可以看出,T 检验的假阳性(I 型错误)比例实际上非常低。有意思。与我们从对数正态分布中取样相比,这似乎是相反的效应模式。
你可能会说,“那么,较低的假阳性率是好的,对吗?”嗯,是的,但是我们正在创造一个新的问题……
为了简单起见,我略读了一些技术细节,但让我这么说吧:通常,研究人员对 5%的假阳性率“还行”。如此严格的比率反映了我们希望在假阳性方面稳妥一些。我们正在优化低假阳性率,但代价是假阴性(第二类错误)。
上面图表中的测试旨在将假阳性率保持在 5% (0.05)。任何高于 5%的都意味着太多的假阳性。任何远低于 5%的数字都意味着太多的假阴性(缺乏敏感性或统计能力)。
总而言之,与百分位数自举检验等稳健检验相比,T 检验在以下方面存在问题:
- 当采样来自偏斜、重尾或轻尾分布时,I 型误差会增加。
- 当采样来自对称的重尾分布时,功耗会降低。
我们还可以说更多,特别是在低概率覆盖率(中间的 95%)的含义方面,以及事实上 t 是并不总是以为零为中心,但是如果你感兴趣,你可以在这里阅读更多关于那个的内容。只要说有更好的选择就够了!
T 检验的可靠替代方法
好的,百分位数自举测试,你在上面已经看到了,在第一类错误和功效方面都表现得很好。与假设正态性的传统测试(以及基于平均值和标准偏差的测试)相比,这一点尤其正确。百分位自助检验没有的正态假设。
百分位引导测试很容易描述(另外,我将在后面介绍一个 Python 库用于计算)。首先,下面是比较两个独立组时的步骤:
- 用第一组中的替换值 n 随机重新采样。
- 用第二组中的替换值 n 随机重新采样。
- 计算两个重新取样组的平均值之差。
- 存储得到的平均差值,并多次重复步骤 1-3。
- 如果存储值的中间 95%(置信区间)包含 0,则两组之间没有统计差异。否则,你会发现不同之处。
是的,就是这样。感谢现代计算机,像这样的大规模迭代是微不足道的。我们不是假设一个特定的形状来悬挂我们的假设,而是简单地建立我们自己的经验决定的抽样分布。
引入假设
假设是一个用于稳健统计的同行评审 Python 库。它基于 Rand R. Wilcox 的R 方法集合这里。假设有许多稳健测试的功能,包括单因子和双因子设计的方法以及测量关联的方法。文档提供了可运行示例的“启动链接”,因此您可以立即使用这个库。
计算百分位自举测试
我们看到了百分位数自举测试是多么容易用语言来描述,但是让我们看看用假设来计算是多么容易。同样,假设我们正在比较两个独立的组。
让我们导入两样东西:
- 一种称为修整均值的稳健估计量。这比使用样本平均值要好得多,因为你可以在这里阅读和。
- 测试本身,也就是所谓的
pb2gen。我使用 Wilcox 的命名约定—pb2gen代表“广义 p 值的百分位数自举”,以防您感到疑惑。
from hypothesize.utilities import trim_mean from hypothesize.compare_groups_with_single_factor import pb2gen
然后,我们简单地传递 Pandas 数据帧的列(代表两个组)、修整平均值和修整量(在本例中为 20%)。
results=pb2gen(df.column_1, df.column_2, trim_mean, .2)
Hypothesize 返回的结果字典如下:
{
'ci': [-0.22625614592148624, 0.06961754796950131],
'est_1': 0.43968438076483285,
'est_2': 0.5290985245430996,
'est_dif': -0.08941414377826673,
'n1': 50,
'n2': 50,
'p_value': 0.27,
'variance': 0.005787027326924963
}
在结果字典中,我们可以看到置信区间和 p 值。让我们看另一个例子。置信区间包含 0,所以我们说组间没有统计学差异。
计算稳健相关性
有两个定量栏?这对于一个成功的皮尔森 r 相关来说是一个完美的工作——相信我,如果你认为 T 检验有问题,你肯定会想读读关于相关性和回归是如何受到未满足的假设和异常值的影响(它变得很糟糕)。
from hypothesize.measuring_associations import wincor
results=wincor(df.column_1, df.column_2, tr=.2)
这就是全部了。这是结果字典,显示了相关性和重要性,以及其他细节。
{
'cor': 0.08515087411576182,
'nval': 50,
'sig': 0.558539575073185,
'wcov': 0.004207827245660796
}
行动呼吁
过去,统计学家理所当然地依赖正态性等便利的假设。我是说,你能想象手工做 5000 个 bootstrap 样本吗?!当然不是。传统的推理测试,尤其是那些依赖于均值和方差的测试,确实有严重的缺点。幸运的是,对稳健替代方法的研究已经超过 50 年,计算这些新方法的软件也变得越来越容易获得。
考虑在你的技能中加入稳健的统计数据。关于这个主题的权威书籍是 Wilcox 的稳健估计和假设检验的介绍。如果你喜欢不太专业的东西,试试现代统计方法的基础。
如果你想马上体验一下,Deepnote 可以让你轻松地投入学习。尝试这个笔记本开始玩本文中使用的模拟。或者,尝试这一个来探索来自假设的一些健壮的方法——不需要安装或设置。
享受更强大和准确的统计数据!
“这是你吗?”具有大型语言模型的现代数据堆栈中的实体匹配
液体火箭发动机的生产试验
壮举。Avanika Narayan

“用人工智能识别人的系统,以黑客帝国的风格”[图片由 DALLE2 生成]
数据仓库时代的实体匹配
T 数据宇宙至少和漫威宇宙一样忙于生产一个又一个闪亮的新东西:在“现代数据栈”(MDS)开始使用雪花和 dbt 之后,大量的工具一直在帮助人们连接和管理他们在 MDS 内的所有数据源。您有来自 Salesforce 的客户数据吗?完美!你有谷歌分析的广告数据吗?带他们进来!你有 WorkDay 的员工数据吗?多多益善!
举个具体的例子。假设您正在为您的预测和报告功能合并供应商反馈,例如,反馈会告诉您产品 x 将有多少库存。虽然不同反馈的产品的确切的形状和形式不同,但通过检查,人们通常能够确认产品 A 和 B 是相同的,而产品 C 不是(它是一个三脚架,但显然不是同一件物品):
- 产品 A :新-targus 红色 tg-6660tr 三向云台三脚架 66-meytg 6660 tr,targus 生产,价格 31.0
- 产品 B : targus 生产的 targus red tg-6660tr 三向云台三脚架,价格 29.98
- 产品 C :新黑灰色 flexpod 手爪三脚架,球头,sunpack 生产,无价格
产品元数据以一种不明显的、难以预测的方式变化:价格可能略有不同,属性可能会出现和消失,某些商品的数据可能会丢失。虽然写下规则和模糊匹配来摆脱它肯定是可能的,但这需要大量的时间和代码(编写、测试和维护)。
在一个很远很远的星系中,一种新的 NLP 模型已经学会了自然语言的(一些)细微差别(以及关于我们世界的事实!)通过在 45tb 的自然语言 web 文本(例如,Reddit、维基百科等)上学习预测句子中的下一个单词(例如,“猫在_ _ _ _ _ _”)。令人惊讶的是,非常大的神经网络,可以解决我们的猫完成也学会了如何解决广泛的 其他任务太:
如果我们告诉你同样的技术也可以解决实体匹配呢?
特别是,NLP 领域的某人最近认识到实体解析是这些模型可以解决的许多任务之一:向模型描述两个项目,然后要求完成类似“项目 A 和项目 B 是同一个项目吗?_____"!
在这篇文章中,我们(Jacopo + Avanika)分享了一个开源 repo ,它在 MDS 实现了一个实体解析管道,由 GPT-3 ,一个大型语言模型(LLM):不需要机器学习或 MLOps 的知识——管道在 SQL 中的 dbt 上作为雪花外部函数运行。我们唯一需要的 Python(大约 20 行)是为连接我们的仓库和 LLM 的 AWS Lambda 提供动力。
除了为期一天的项目的乐趣之外,使用 GPT3 进行构建让我们得以一窥其存在严重缺陷、悲剧性地不完整但却令人着迷的数字大脑的内部。虽然这种方法令人惊讶地工作,我们讨论了我们的设置目前的一些限制,希望兴奋的读者将开始使它变得更好。
克隆回购 ,在 Github 上给当明星,一起唱!
问题设置
事实证明,既然我们在 Salesforce、WorkDay、Marketo 和无数其他来源中都有 John Smith,那么连接表就很困难:来自纽约的 John Smith 和来自曼哈顿的 John P. Smith 是同一个人吗?换句话说,实体解析的问题开始出现:数据只有在可连接的情况下才有用,但是现在连接不再那么确定了。
实体解析的任务是协调我们的提要之间的信息,通过这种方式,我们可以跨提要匹配两个相同的产品,并将其余的产品标记为唯一的:虽然我们的示例以产品为特色(即,在 BSD 3 条款许可下,在开源 deepmatcher repo 中可用的 Amazon-Walmart 数据集),但是对于 Salesforce 和 Crunchbase 中的公司,或者中的雇员,也会出现相同的挑战
实体解析当然不是一个新问题,但在 MDS 环境中它(算是)是一个新问题,以前的解决方案可能不太管用:字符串操作和简单的基于规则的模糊匹配很难用 SQL 表达,而且构建和维护它们的成本很高;机器学习解决方案需要标签和建模工作,以及一系列不一定与 MDS 主要角色、数据工程师和分析工程师重叠的技能。
[如果你想了解以“手动”方式做事的复杂性,这个是一个很棒的演讲,可以让你获得一些视角。]
在没有专门的 ML 管道的情况下,我们需要解决的难题来自于“基金会模型能争论你的数据吗?”(仅供参考,“基础模式”为 LLM 的 Stanford-ese);特别是,我们发现大型语言模型,如 OpenAI GPT-3,如果作为问答问题,可以解决实体解析。虽然对 LLM 的全面解释已经超出了本文的范围,但是记住 LLM 可能看起来令人生畏,但是它们确实只是接受文本作为输入,并产生更多文本作为输出的系统。
LLM 所能做的就是根据我们提供给它们的一些文本生成文本。
它们有用的诀窍确实在我们的语言中:事实上,英语非常灵活,我们可以将标准的 ML 问题转换成问题/例子,并要求 LLM 生成我们寻求的答案作为“正确的文本延续”。例如:
- 我们可以通过提供带有相应标签的句子作为示例来解决一个情感分析问题(“我讨厌这里的食物:情感是负面的”),然后要求 LLM 为一个新句子生成完成(“我喜欢披萨:情感是 _____”)。
- 我们可以通过要求模型“将这个翻译成法语”,然后等待它为目标句子生成适当的完成来解决机器翻译问题:“你有哪些房间可用?_____”(正如我们在这里所做的)。
- 最后,我们还可以将实体匹配转化为(大致)完成句子“这些产品是一样的吗?_____”,用文字描述完这些产品后。

如果我们问了正确的问题,LLM(“FM”代表基础模型)可以处理实体匹配[原始图片来自 arxiv 论文,由本文作者之一合著]。
如果这是真的,我们“只是”需要一种方法来将从事 MDS 的分析工程师连接到 GPT-3 的推理 API:一旦设置完成,灵活的实体解析算法将作为标准 dbt 管道的一部分用于数据转换。雪花外部函数允许我们建立这个桥梁,并使流程神奇地工作。
将碎片连接在一起
大部分设置是将 Snowflake 连接到 AWS 支持的端点(查看 repo 了解详情)。一旦这样做了,分析工程师的开发者体验就是她所了解和喜爱的基于 SQL 的 DAG。当您键入 dbt run 时:
- 初步转换准备原始数据并标准化产品元数据。
- 最后一个转换调用 SQL 中的,一个包装端点的函数。
- 当到达终点时,几行 Python 代码为 GPT-3 准备元数据作为一个问题,并通过 OpenAI API 运行它以获得响应。
- 解析后的响应成为雪花表中的布尔值,表明行中的产品对实际上是否相同。

提议的实现的功能工作流程:左边是 MDS,GPT 3 号通过 API 调用提供的智能,由 Lambda 代理。[图片由作者提供]
AWS lambda 与 OpenAI 和雪花到端点的连接都是一次性的过程,可以由一个数据工程师在几分钟内完成。然后,实际的实体解析算法在一个 SQL 命令中被抽象出来,可供不了解 Python、AWS 和机器学习的分析工程师使用:
SELECT
external_functions.lambda.resolution(em.SERIALIZED_A, em.SERIALIZED_B) AS RESOLUTION,
em.*
FROM
matching_input AS em
它只是工作。
正如我们的朋友皮耶罗(Piero)和克里斯(Chris)有力地论证的那样,“机器学习的未来将取决于它掌握在我们其他人的手中”:SQL 的声明性本质和声明性 ML 的兴起(例如pred base)之间的联姻有望将机器学习系统带入分析世界。随着大型语言模型的效率和可移植性的提高,数据堆栈的每个部分都将受益于少量预测。
有用吗?
该论文报告了在经过一些提示性的体操之后相当令人鼓舞的准确性——也就是说,在实验了问题回答问题应该如何真正被设计,以及我们应该向 GPT-3 提供哪些例子之后:

与标准行业系统相比,在沃尔玛-亚马逊数据集和其他几个数据集上的实体匹配结果。[原始表格来自arxiv 论文,由本帖作者之一合著]。
虽然即时调优仍然是艺术而不是科学,但它比标准 ML 设置中所需的等效调优有一个明显的优势:ML 调优是 Python 和数学的结合,而即时调优只需要英语。分析工程师不需要学习新的语言或工具,只是简单地调整他们的问题(毕竟,“我语言的限制意味着我世界的限制”。
如果你想看到系统的运行,GitHub repo包含了一步一步的截图和完整的指令来重现管道。

仓库中的最后一个表:LABEL 是来自原始数据集的金色标签,RESOLUTION 是来自 GPT3 的答案,SERIALIZED_A / B 包含发送到 LLM 的产品序列化。[作者截图]
局限性和未来工作
在 MDS 内运行 GPT-3 算法的明显限制是成本和规模:如果你一天需要数万次预测,当前的流量将使你的 DAG 更慢和明显更昂贵。然而,尽管还不太实用但,我们发现这种方法的优雅及其诱人的潜力很吸引人:
灵活的、通晓世界知识的模型,其神奇的力量可以被没有受过 ML 训练的从业者用 SQL 唤起。
在 MDS 和 LLM 的交叉点上是否有一整套功能等待被发现和大规模生产?
虽然这个问题的答案肯定超出了本项目的范围,但我们对进一步污染数据操作、可扩展工程和大型语言模型的前景感到兴奋:
“你可以给思想定价。有些花费很多,有些花费很少。一个人如何支付思想的费用?我认为,答案是:有勇气”。
再见,太空牛仔
这篇文章是由阿瓦妮卡和雅格布共同完成的,也是(J)正在进行的用维特根斯坦的话交换网球课(A)的计划的一部分。
当然,如果没有这篇精彩的论文(作者阿瓦妮卡与伊内斯·横山雅美、劳雷尔·奥尔和克里斯托弗·雷)、雪花的可扩展性和 OpenAI APIs,这篇文章是不可能发表的。
如果你(在工业界或学术界)正在从事数据和 LLM 的交叉工作,请 务必联系 、我们计划下一步该怎么做!
为什么你的数据可视化应该是色盲友好的
尤其是当你试图说服男人

图片由作者提供。
你以前遇到过这种情况吗?你以为你的数据分析报告很有说服力。数据支持你的论点,你完善了你的数据可视化。但是你需要说服的人就像“咩”一样。
你的论点不够有力吗?还是你的数据可视化没有你想象的那么有效?单一的调色板使得色盲者难以理解数据可视化。例如,红色和绿色的组合使得数据可视化对于患有该色盲的人来说无效。
色盲是一种遗传疾病,使得人们很难区分特定的颜色,最常见的是红色和绿色。色盲影响着大约 1/12(8%)的男性和 1/200(0.5%)的女性[2]。在全球范围内,大约有 3 亿人受到不同程度的色盲影响——这大约相当于美国的人口[2]。
色盲影响着大约 1/12 的男性和 1/200 的女性。
这意味着很有可能你的观众中有人对你的数据可视化体验与你想象的大相径庭,尤其是如果你的观众主要是男性的话。例如如果你试图说服马克·扎克伯格,你最好不要使用红色和绿色的组合,因为据说他很难区分这些颜色。在最糟糕的情况下,您选择的颜色甚至会使您的数据可视化对某些观众来说难以理解,从而妨碍您进行令人信服的论证。
即使你的观众没有视觉缺陷,黑白打印对你的数据可视化的影响可以和完全色盲的人(是的,人们还在打印)类似。
在本文中,您将学习如何使您的数据可视化易于访问,以确保您的观众中的每个人都能完全理解您试图传达的信息,以实现您的目标。我们首先快速概述不同类型的色盲,看看糟糕的颜色选择如何使您的数据可视化无法说服色盲患者。然后,我们将讨论两种策略,通过利用正确的调色板和其他视觉辅助工具来创建更易访问的数据可视化。
什么是色盲?
色盲是一种遗传疾病,使得人们很难区分红色、绿色或蓝色的光。然而,一小部分色盲者根本看不到任何颜色。
色盲的三个主要类别是红绿、蓝黄和完全色盲【2,3】。对于红绿和蓝黄色盲,色盲的类型可分为受影响的颜色(红、绿、蓝)和严重程度(异常:不规则或无视:失明)。
红绿色盲
最常见的一类是红绿色盲。而正常 (红光弱),是对红光的敏感度降低,异常【绿光弱】是对绿光的敏感度降低。红盲和绿盲都让你根本分不清红色和绿色。因此,红色和绿色以及蓝色和紫色的颜色组合对于有这些情况的人来说是有问题的[2,3]。

红绿色盲(图片由作者提供,色盲由科布利斯【1】模拟)
蓝黄色盲
蓝黄色盲比红绿色盲少见。虽然三色异常(蓝弱)是对蓝光的敏感度降低,但三色异常(蓝盲)让你无法区分蓝色和绿色、紫色和红色、黄色和粉红色[2,3]。

蓝黄色色盲(图片由作者提供,色盲由科布利斯【1】模拟)
完全色盲
完全色盲非常罕见[2,3]。完全色盲或色盲的人看不到任何颜色。

完全色盲(图片由作者提供,色盲由科布利斯【1】模拟)
选择可访问的调色板
让我们看一个由于调色板而导致的无效数据可视化的例子。第一眼看上去,下面的数据可视化看起来可以有效地展示 D 列中的值是正还是负。这是一种表示积极与消极,甚至是好与坏的流行方法,因为我们经常把绿色和“好”联系在一起,而把红色和“危险”联系在一起。对于视力正常的人来说,很明显,第 1 行具有最大的负值-0.99,第 3 行具有最大的正值 0.75。

红-黄-绿渐变用于区分 d 列中的正值和负值。(图片由作者从 Kaggle 获得)
然而,对于红绿色盲的人来说,上面的数据可视化很难阅读。下面你可以看到之前用红绿色盲模拟的数据可视化。最后三排有相似的棕黄色阴影。与视力正常的人看到的相比,很难识别最后一行是负值,而上面的两个值是正值。

模拟红绿色盲(图片由作者从 Kaggle 转换成科布利斯【1】)
虽然有不同类型的色盲,但选择一个无障碍的调色板并不困难。首先,确保避免红色和绿色、蓝色和紫色、蓝色和绿色或红色和黄色等有问题的颜色组合。此外,像 ColorBrewer 和 Coolors 这样的在线资源可以帮助你挑选色盲友好的调色板。
在 ColorBrewer 中,您可以将选项设置为仅显示左侧工具栏中的色盲友好调色板。

ColorBrewer 截图
在coolers中,您可以通过点击工具栏中的眼镜符号来启用色盲模拟器。

酷派截图
如果您没有时间创建自定义调色板,Seaborn 可视化库提供了一个现成的色盲友好调色板[5]。您可以使用以下代码片段设置调色板:
sns.color_palette("colorblind")
下面你可以看到改进的数据可视化,它有一个色盲友好的调色板(在这个例子中我们用的是“coolwarm”)。不仅对于视力正常的人来说,而且对于红绿或蓝黄色盲的人来说,正值和负值都很容易区分。

通过色盲友好的调色板改进数据可视化。左:正常视力。右图:模拟红绿色盲。(图片由作者从 Kaggle 用科布利斯【1】转换而来)
然而,即使有了色盲友好的调色板,对于完全色盲的人来说,上面的例子也很难理解。
不要只依赖颜色
虽然您可以通过选择更好的颜色组合来使您的数据可视化更容易访问,但您不能只依赖颜色。黑白打印对数据可视化的影响,就像一个完全色盲的人所经历的一样。虽然左边的数据可视化有一个色盲友好的调色板,但它不适合完全色盲或黑白打印的人。

左:数据可视化与色盲友好的调色板。右图:模拟完全色盲的相同数据可视化(图片由作者用科布利斯【1】转换)
为了确保您的受众能够完全理解您的数据可视化,除了使用色盲友好的调色板之外,您还可以使用以下策略。
- 使用不同色调的颜色
- 使用不同的标记形状、线条样式和宽度以及填充图案
- 添加文本
下面你可以看到,结合这些策略不仅为完全色盲的人,也为视力正常的人增加了可读性。

适用于黑白打印或完全色盲的人的改进的数据可视化。(图片由作者提供)
有了这三种策略,对于色盲和黑白打印的人来说,数据可视化的可读性提高了。

模拟完全色盲,改善数据可视化。(图片由作者用科布利斯【1】转换而来)
结论
这篇文章解释了为什么不可访问的调色板会使数据可视化对色盲者无效。由于色盲影响了 8%的男性和 0.5%的女性,你的观众中很有可能有人是色盲。因此,为调色板考虑色盲有助于您的数据有效地讲述一个故事。
像 ColorBrewer 和 Coolors 这样的在线资源可以帮助创建一个可访问的调色板。你也可以用科布利斯【1】来模拟你的数据可视化对各种色盲的有效性。
考虑到完全色盲或者黑白打印,不能只靠颜色。因此,用不同的色调、标记形状、线条样式、填充图案和文本来支持色盲友好的调色板,可以提高数据可视化的效率。
下图总结了本文的要点:

色盲类型总结(图片由作者从科布利斯【1】获得灵感)
喜欢这个故事吗?
要阅读更多来自我和其他作家的故事,请在 Medium 上注册。报名时可以用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。
https://medium.com/@iamleonie/membership
参考
1科布利斯,《色盲模拟器》color-blindness.com。http://www . color-blind . com/coblis-color-blind-simulator/(2022 年 7 月 8 日访问)
[2]色盲意识,“色盲的类型。”colourblindawareness.org。https://www . colour blind awareness . org/colour-blind/types-of-colour-blind/(2022 年 7 月 8 日访问)
[3]国家眼科研究所,“色盲的类型。”nei.nih.gov。https://www . nei . NIH . gov/learn-on-eye-health/eye-conditions-and-diseases/color-blindness/types-color-blindness(2022 年 7 月 8 日访问)
[4] J. A. Vargas,“脸书的面貌”newyorker.com。https://www . new Yorker . com/magazine/2010/09/20/the-face-of-Facebook(2022 年 7 月 8 日访问)
[5] M. Waskom,“选择调色板”pydata.org。https://seaborn.pydata.org/tutorial/color_palettes.html(2022 年 7 月 8 日访问)
您的数据团队被困在救火中了吗?也许他们应该。
如果雇佣更多的数据科学家和推广自助式报告不起作用,有一种组织结构可能会起作用。

[图片来自突发的妮可·德·霍斯
作为一名数据科学家,当你在某些因素不受你控制的业务线工作时,很容易陷入一种感觉像是一直在救火的境地。竞争对手可以发布新功能,要求产品团队争夺数据以做出适当的响应。欺诈者可能会破坏您要求快速响应以防止客户资金损失的缓解措施。即使在内部,高管做出的战略决策也可能需要突然的、计划外的工作爆发。
如果你在日常工作中遇到这样的事情,你并不孤单。这些年来,我从同事那里听到的最常见的抱怨之一是,他们总是处于一种争先恐后地处理新请求的状态,没有给他们时间去做他们认为会真正推动组织前进的长期工作。他们希望 T4 能有其他人代替他们去救火。通常,自助 BI 工具和反向 ETL 被视为解决这一问题的方法,让数据部门之外的员工能够做报告。但我认为这忽略了大局。
事实是,在任何规模的组织中,都会有大量计划外的工作。虽然在某些情况下,这可能指向组织混乱,反映了潜在的领导问题,但通常情况并非如此。开篇提到的因素是工作中不可避免的一部分,如果能够有条理地完成,快速反应是一种竞争优势。那么,谁应该处理这些情况呢?喜欢它的人!
因为大多数人不喜欢跑进着火的大楼,所以很难理解那些喜欢的人的想法。然而,许多人选择的高压力职业实际上或象征性地就是做这些。类似地,虽然普通的数据科学家或分析师可能会因为一直处理高优先级的请求而承受巨大的压力,但也有一些人以此为乐。也许是在别人真正需要的时候帮助他们的感觉,接近行动,或者是几乎每天解决一系列新的重要问题的快感让他们乐在其中。不管是什么原因,重要的是找到这些人,让他们做自己喜欢做的事情。
您如何让这些人融入整个组织,这个角色与“常规”数据科学职位有什么不同吗?答案是,这根本不是不同的角色,而是将现有角色组合在一起的不同方式。我见过的一种行之有效的方法是让不同的小组处理您需要涵盖的领域的重要组成部分:
- 快速响应团队:一组数据分析师和科学家,致力于尽快发现问题并做出响应。这个团队产生的短期解决方案是可以的,因为重点是使用当前可用的任何工具来提高速度。
- Preemption team:由数据科学家和分析工程师组成的团队专注于构建模型,以减少快速响应团队需要做的工作量。这里强调的是通用的、构建良好的、高性能的和易于维护的长期解决方案。
- 工程团队:他们的工作是识别影响快速响应和抢占团队的工具瓶颈,并填补这些缺口。每次他们运送一些东西,其他团队就会变得更好。
有了这种结构,每个团队都可以雇佣喜欢这种特殊技能的人,而且,每个人都可以更好地控制他们的工作。这些问题被包含在快速响应团队中,而不是占用单个统一数据科学团队的所有时间,快速响应团队由数据科学家和其他喜欢这种工作的人组成。他们可以在其能力范围内对任务进行优先级排序,并向抢占和工程团队提供关于他们需要更多覆盖的反馈。与此同时,这些其他团队可以专注于长期建设,以确保快速响应团队不会因为试图自己解决太多问题而耗尽精力。
这是否意味着自助分析没有任何作用?绝对不行。反向 ETL 仍然是一个有价值的工具,可以减轻团队的工作量。你可以把它想象成从给消防栓供水的总水管中分出的进入人们家中的“常规水管”。这些是对数据团队所做工作的重要补充,但并不意味着当事情超出基本报告所能处理的范围时,可以完全取代经验丰富的数据团队在其组织内为关键决策和模型所扮演的角色。
虽然我上面给出的例子在一家中型公司中运行良好,但回顾我工作过的其他团队,我也可以看到它在不同的情况下运行,例如在快速增长的初创企业或大型公司的高增长领域。和往常一样,您可能需要调整这个模型以适应您的特定环境,但如果您发现自己不堪重负,并且雇佣更多的人或试图将工作交给其他组织并不能解决您的问题,这是一个很好的起点。
你的数据集不平衡吗?
原文:https://towardsdatascience.com/is-your-dataset-imbalanced-292d2a0a1321
查看数据集是否不平衡的一些技巧

作者图片
对于数据科学家来说,处理不平衡的数据集总是很困难。如果我们处理不当,这样的数据集可能会给我们的机器学习模型带来麻烦。因此,在采取适当的预防措施之前,测量数据集的不平衡程度非常重要。在这篇文章中,我建议一些可能的技术。
我们如何定义“不平衡”?
当一些目标类的频率比其他目标类低时,我们说分类数据集是不平衡的。
例如,让我们看看 iris 数据集的目标变量的分布。

作者图片
正如我们所见,频率都是相同的,数据集完全平衡。
现在让我们更改数据集,使用乳腺癌数据集。

作者图片
正如我们所看到的,这两个类有不同的频率,所以数据集是不平衡的。
所以,一般来说,不同的频率让我们可以说数据集是不平衡的。然而,通常的经验导致定义为“不平衡”的数据集,其中目标值具有非常不同的频率。
事实上,一般来说,稍微不同的频率对大多数模型来说并不是一个大问题。当一个或多个类别的频率非常低时,问题就出现了。在这些情况下,模型可能无法正确地学习如何处理它们。
像往常一样,我们必须量化“非常小”是什么意思。让我们看一些技术来评估我们是否应该考虑一个不平衡的数据集。
数据可视化
数据可视化可以给我们一些非常有用的见解,那么我们为什么不应该将它用于这项任务呢?
我建议的方法是用类的频率和它们的 95%置信区间绘制一个条形图。记住,给定一个频率 p ,事件总数等于 N ,95%置信区间计算如下:

给定 m 类,期望频率为 1/m 。因此,我们可以绘制置信区间和与预期频率相关的水平线,并查看它是否穿过置信区间。
我们先导入一些库和“乳腺癌”数据集。
import numpy as np
import pandas as pd
from scipy.stats import norm,chisquare
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer as d X,y = d(return_X_y = True)
现在,让我们用类的频率、95%置信区间和预期频率绘制条形图。
freqs =pd.Series(y).value_counts() /len(y) std_errors = np.sqrt(freqs*(1-freqs)/len(y)) expected_frequency = 1/len(np.unique(y)) freqs.plot(kind='bar',yerr=std_errors*1.96)
plt.axhline(expected_frequency,color='red',linestyle='--')

作者图片
正如我们所看到的,间隔距离预期的频率很远,所以数据集平衡的置信度只有 5%。相当小。
如果我们对“葡萄酒”数据集重复这个过程,结果如下。

作者图片
现在,对于类别 0,期望的频率线正确地穿过置信区间,但是对于类别 1 和 2,期望的频率线仅仅略微穿过置信区间。所以我们可以说,在 95%的置信度下,类 0 是平衡的,而我们不确定类 1 和类 2。
这种简单的方法可以给我们清晰的图形证据,这在数据科学中总是一个好主意。
z 检验
对于那些喜欢使用 p 值的人,我们可以计算 t 尾 z 检验的 p 值,以评估给定频率和预期频率之间的统计差异。我们必须对每门课进行 z 测试。零假设是频率等于预期频率(即数据集是平衡的)。
在等于 N 的多个事件上,将给定频率与预期频率 1/m 进行比较的 z 变量为:

对于双尾 z 检验,p 值为:

我们乘以 2,因为这是一个双尾测试。
在 Python 中,我们可以写:
for target_val in freqs.index:
z = (freqs[target_val] - expected_frequency)/ std_errors[target_val] print("Class:",target_val) print("p-value:",norm.cdf(-np.abs(z)))
print("----")
对于乳腺癌数据集,结果是:

作者图片
p 值非常小,因此我们拒绝表示数据集平衡的零假设。
对于葡萄酒数据集,我们得到:

作者图片
这些结果完全符合图形分析。类别 1 和类别 2 的 p 值较小,我们拒绝零假设。类 0 的 p 值很高,我们不拒绝零假设。
卡方检验
如果我们想对整个数据集使用单个测试,而不是对每个类使用一个测试,我们可以使用 Pearson 的卡方检验。
如果我们有 m 个类和 N 个总记录以及每个类作为记录,我们可以通过计算统计量来计算这样一个测试的 p 值:

该变量分布为具有 m-1 自由度的卡方分布。我们可以进行的测试是单尾测试。无效假设是数据集是平衡的。
在 Python 中,我们只用一行代码就可以做到:
chisquare(pd.Series(y).value_counts()).pvalue
对于乳腺癌数据集,结果是 1.211e-09。所以我们拒绝零假设。对于葡萄酒数据集,p 值为 0.107,因此我们不拒绝零假设。
请记住,卡方检验只有在您的预期出现次数很大(即大于 20)时才有效,否则其背后的近似值将不再可靠。
一些实用的建议
我的建议是始终使用数据可视化。这很清楚,没有偏见,所以它总是一个好主意。但是,如果您的同事需要一个 p 值,您可以使用 z 检验来逐类评估 p 值。否则,您可以使用卡方检验计算总体结果。
结论
在这篇文章中,我提出了一些评估数据集是否不平衡的技术。选择适当的技术将为您提供不同的见解,并引导您采取不同的策略来处理不平衡的数据(例如,SMOTE 重采样或类分组)。
原载于 2022 年 6 月 26 日 https://www.yourdatateacher.comhttps://www.yourdatateacher.com/2022/06/27/is-your-dataset-imbalanced/。
你的模特是最好的还是最幸运的?
原文:https://towardsdatascience.com/is-your-model-the-best-one-or-the-luckiest-one-7cfd1f43ea6
如何避免在选择最佳模型时被随机性所迷惑

[图片由作者提供]
我们已经习惯了 Kaggle 中的数据科学挑战,ROC 分数 0.1%的变化就能决定赢得 10 万美元还是一无所获。
以数据科学碗 2017 挑战赛为例。第一名的奖金为 50 万美元,第二名为 20 万美元,第三名为 10 万美元,以此类推。选择的评估标准是对数损失。这是最终的排行榜:

[截图来自 Kaggle
现在,你认为第一名的模特和第二名的模特有什么不同?
如果你的回答是:“区别在于第一个模型比第二个模型更好,因为它的对数损失更小”,那么你可能有些操之过急了。事实上,
我们如何确定测试集上更好的度量意味着更好的模型,而不仅仅是更幸运的模型?
我举了一个关于 Kaggle 的例子,但是同样的推理也适用于任何现实生活中我们需要选择一个模型的情况。
对于数据科学家来说,知道在模型选择中哪个部分是偶然发挥作用的是一项基本技能。在本文中,我们将看到如何量化选择最佳模型过程中的随机性。
实际上,“最佳模式”是什么?
首先,我们需要一个关于我们所说的“最佳模式”的明确定义。
假设我们有两个模型,A 和 B,我们想选择最好的一个。我们都同意最好的模型是在看不见的数据上表现最好的模型。
因此,我们收集了一些测试数据(在训练中没有使用)并在此基础上评估我们的模型。假设模型 A 的 ROC 分数为 86%,模型 B 的 ROC 分数为 85%。这是否意味着模型 A 优于模型 B?暂时来说,是的。
但是想象一下,过了一段时间,您收集了更多的数据,并将其添加到之前的测试集中。现在 A 型还是 86%,B 型已经提高到 87%。在这一点上,B 比 a 好,怎么可能?
显然,唯一明确的定义如下:
对于给定的任务,最佳模型是对所有可能的不可见数据表现最佳的模型。
这个定义的重要部分是“所有可能的”。事实上,我们总是可以访问有限的数据,所以我们的测试数据集只是所有可能的未知数据的一小部分。这就像说我们永远不会真正知道什么是最好的模型!
为了处理这个问题,我们需要一个新的概念。
介绍宇宙
从现在开始,我们将把所有可能看不见的数据集称为“宇宙”。在现实世界中,我们永远无法观测到宇宙,只能观测到从宇宙中随机抽取的一个测试数据集。

我们从来不观察宇宙,我们只观察一个单一的测试数据集,它是一个小的,随机的部分。[图片由作者提供]
一个模型真正的表现是它在宇宙上的表现。在这种情况下,模型的真实 ROC 得分为 80.4%。然而,我们永远无法观察宇宙,因此,我们永远无法观察模型的真实 ROC。
我们所能观察到的是在测试集上计算的 ROC 分数。有时候会高一点(81.6%),有时候会小一点(79.9%和 78.5%),但是我们没有办法知道真实的 ROC 评分和观测的 ROC 评分有多远。
我们所能做的就是尝试评估这个过程中有多少随机性。为了做到这一点,我们需要模拟宇宙,并从中抽取许多随机测试数据集。这样,我们就可以量化观察到的分数的离差。
如何模拟宇宙?
我们的目标是获得一个具有给定 ROC 分数的观察宇宙。事实证明,有一个非常简单的方法可以做到这一点。
首先,我们需要设定宇宙中期望的个体数量(通常是一个很大的数字)。然后,我们需要设置流行度,即阳性百分比(我们可以将其保留为 50 %,这是默认设置)。第三步是选择我们希望在宇宙中的 ROC 分数。
最后,我们可以计算宇宙中每个个体的预测概率:负数必须均匀分布在 0 和 1 之间,而正数必须均匀分布在α和 1 之间。

如何获得具有任意 ROC 分数的数据集?[图片由作者提供]
其中,α可通过以下公式从 ROC 中获得:

当 ROC 大于或等于 50%时,α和 ROC 之间的关系。[图片由作者提供]
ROC 为 50%时,α为 0,表示阴性和阳性的分布是相同的。反之,当 ROC 为 100%时,α为 1,意味着所有的阳性都集中在 1 上:阴性和阳性之间没有重叠。
在 Python 中,这可以转化为以下函数:
def **get_y_proba**(roc, n=100000, prevalence=.5):
**'''Get two arrays, y and proba, for a given roc (greater than .5)'''** n_ones = int(round(n * prevalence))
n_zeros = n - n_ones y = np.array([0] * n_zeros + [1] * n_ones) alpha = (roc - .5) * 2 proba_zeros = np.linspace(0, 1, n_zeros)
proba_ones = np.linspace(alpha, 1, n_ones)
proba = np.concatenate([proba_zeros, proba_ones]) return y, proba
获取我们的不确定性度量
既然我们已经有了创建合成宇宙的方法,让我们用下面的命令来获得我们的宇宙:
y_universe, proba_universe = get_y_proba(roc=.8, n=100000, prevalence=.5)
因此,我们的宇宙由 100,000 个观察值组成,其中一半是正面的,ROC 得分为 80%。
现在,让我们模拟不同测试集的提取。我们将提取 5000 个不同的测试集,每个测试集由来自宇宙的 1000 个观测值组成。这是相应的代码:
rocs_sample = []for i in range(5_000):
index = np.random.choice(range(len(y_universe)), 1_000, replace=True)
y_sample, proba_sample = y[index], proba[index]
roc_sample = roc_auc_score(y_sample, proba_sample)
rocs_sample.append(roc_sample)
这是观察到的 ROC 分数的分布:

在来自 ROC 分数为 80%的宇宙的不同测试集上观察到的 ROC 分数。[图片由作者提供]
正如您所看到的,结果非常不同,从不到 76%到超过 84%不等。
在正常应用中,我们想要回答的问题如下。我有两个模型,一个 ROC 分 78%,一个 82%。它们拥有相同潜在 ROC 的可能性有多大,而这种差异只是偶然的结果?
为了得到一个概念,我们可以计算模拟中每对观察到的 ROC 分数之间的距离。Scikit-learn 有一个函数pairwise_distances允许这样做。
import numpy as np
from sklearn.metrics import pairwise_distances
dist = pairwise_distances(np.array(rocs_sample).reshape(-1,1))
dist = dist[np.triu_indices(len(rocs_sample), k=1)]
让我们在经验累积分布函数中可视化 ROC 得分之间的成对距离。

ROC 评分间成对距离的经验累积分布函数。[图片由作者提供]
第 95 百分位(用虚线突出显示)约为 4%。这意味着两个模型(具有相同的性能)之间的差异只有 5%的时候大于 4%。
因此,用统计学术语来说,我们会说小于 4%的差异不显著!这非常有趣,因为通常我们会认为 82%的 ROC 模型比 78%的 ROC 模型好得多。
为了获得这个概念的另一个可视化,我模拟了三个不同的宇宙,一个 ROC 得分为 75%,另一个为 80%,最后一个为 81%。这些是他们观察到的 ROC 分数的分布。

在来自不同领域的不同测试集上观察到的 ROC 分数分别为 75%、80%和 81%。[图片由作者提供]
很明显,从这个情节来看,最好的模型往往不会赢!想象一下比较几十个模型,每个模型都有不同的真实 ROC 分数。
你实际上不太可能选择最好的模型。很有可能,你会选择最幸运的一个。
我能做些什么吗?
所以你是在告诉我,我不可能 100%确定一个型号比另一个好?这听起来像一场噩梦。当然:数据科学中没有百分百确定的事情。然而,不要绝望。
有理由预计,选择最佳模型的不确定性程度既取决于宇宙的特征,也取决于从宇宙中提取的测试集的特征。特别是,有三个参数决定了不确定性:
- 真实 ROC:在宇宙上计算的 ROC 分数。
- 样本维数:测试集中的观察值数量。
- 样本流行率:测试集中阳性的百分比。
为了了解这些因素对不确定性的影响,我通过为每个因素尝试不同的值来模拟所发生的情况:
- 真实 ROC: 70%,80%,90%。
- 样本维数:1000、5000 和 10000 个观测值。
- 样本患病率:1%、5%和 20%。
因为我们正在为三个参数尝试三个值,这意味着 27 种可能的组合。
对于每个组合,我模拟了一个宇宙,然后对 1000 个不同的测试集进行了采样,并测量了各自的 ROC 分数。然后,我计算了 1000 个 ROC 分数的距离矩阵。最后,我取了距离的第 95 个百分位数(从现在开始称为“d”)。正如我上面所说的,这是选择模型的不确定性的一种度量。
例如,这是 27 次试验中的前 5 次。

27 次试验中的前 5 次。输入是宇宙中的 roc 分数(“ROC”)、观察次数(“n”)和测试集的流行度。输出是 ROC 得分之间距离的第 95 个百分位数(“d”)。[图片由作者提供]
我们用第 95 个百分点来衡量不确定性。该数值越高,比较 ROC 曲线的不确定性越高。
因为我们想知道不确定度如何依赖于 3 个参数,所以测量每个参数和“d”之间的偏相关是很有趣的。这是结果:

每个参数和我们的不确定性测量值之间的部分相关性(观察到的 ROC 评分之间距离的第 95 个百分点)。[图片由作者提供]
称为“r”的列显示了每个参数和不确定性之间的部分相关性。所有相关系数都是负的,表明增加这三个系数中的任何一个都会减少不确定性。特别是,
- 真 ROC。宇宙中较高的 ROC 值意味着较少的不确定性。这是有意义的,因为根据定义,ROC 越高意味着不确定性程度越小。
- 样本维度。增加样本维数可以减少不确定性。这是非常明显的,并且在统计中经常发生。
- 样本患病率。流行程度的提高减少了不确定性。患病率越低意味着阳性越少。越少的阳性意味着随机抽样时权重越大。因此,更大的不确定性。
出于好奇,对于固定的真实 ROC(在本例中为 80%),让我们也可视化当改变样本维度和样本流行度时观察到的 ROC 分数的分布。

样本中 ROC 评分的分布,针对不同的样本维度和样本患病率。请注意,真实的 ROC 得分始终为 80%。[图片由作者提供]
我觉得这个形象不言自明。以左上图为例。样本规模和患病率都非常小:我们有 1000 个观察值和 1%的阳性,这意味着 10 个阳性和 990 个阴性。在这种情况下,不确定性非常高,得到的 ROC 分数分布几乎是均匀的,从 75%到 85%。此外,ROC 得分之间距离的第 95 百分位是 10%,这意味着观察到的 ROC 为 75%和观察到的 ROC 为 85%之间没有显著差异。
然而,随着我们逐步提高样本维度和/或患病率,情况会有所改善,观察到的 ROC 评分分布越来越集中在真实值附近(在本例中为 80%)。例如,通过 10,000 次观察和 20%的患病率,第 95 百分位变成了更合理的 1.2%。
这对我有用吗?
确实是。事实上,即使我们在机会面前都很无助,知道在什么条件下你的结果在统计学上是合理的也是很重要的。
重复我们在上一段中看到的模拟可以帮助您了解测试集的数量和流行程度是否足以检测模型性能之间的真正差异。
你可以在我的 Github 页面中找到本文使用的所有 Python 代码。
如果你想阅读关于这个主题的彻底讨论,更多的是从假设检验的角度(而不是从模拟的角度),你可以阅读劳伦·奥克登·雷纳的这篇博客:“人工智能竞赛不会产生有用的模型”
感谢您的阅读!我希望你喜欢这篇文章。如果你愿意, 在 Linkedin 上加我 !
隔离林—使用 Python 自动检测异常
原文:https://towardsdatascience.com/isolation-forest-auto-anomaly-detection-with-python-e7a8559d4562
使用 Python 的 Scikit-learn 库检测异常值

照片由 pix abay:https://www . pexels . com/photo/black-tree-near-body-of-water-35796/
隔离森林是一种流行的无监督机器学习算法,用于检测数据集内的异常(离群值)。异常检测是任何机器学习和数据科学工作流的关键部分。没有在早期识别的错误值会导致机器学习模型的预测不准确,因此会影响对这些结果的解释。
在这篇短文中,我们将介绍算法的基础,以及如何使用 Scikit-learn 用 Python 轻松实现它。
但是首先,我们需要知道异常值到底是什么。
什么是离群值?
处理真实世界的数据时,经常会遇到超出正常趋势的数据点。这些通常被称为异常值或异常值。检测异常非常有用,原因有很多,包括:
- 检测欺诈性信用卡交易
- 识别可能表明未经授权访问的异常网络流量
- 探测深空图像中有可能成为新星的异常点/像素
- 检测医学扫描中的异常“特征”
在测井测量和岩石物理数据中,异常值可能由于被冲刷的钻孔、工具和传感器问题、罕见的地质特征以及数据采集过程中的问题而出现。
在数据科学/机器学习工作流的早期识别和调查异常值是非常重要的,因为它们可能导致机器学习模型的预测不准确。
Python 中有许多算法,无论是有监督的还是无监督的,都可以用来检测这些异常。
在监督学习的情况下,我们可以使用我们已经检查过并标记为好或坏的数据样本来训练模型,并使用它来预测新的数据样本是否异常。
然而,这些方法具有许多问题,包括减少计算时间所需的小数据集大小和低维度,并且异常在标记的数据中可能是罕见的,导致检测到假阳性。
其中一种无监督的方法叫做隔离森林。关于该算法如何工作的全部细节可以在刘等人(2008)的原始论文中找到,并且可以在这里免费获得https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf。****
隔离林方法
隔离森林是一种基于模型的异常值检测方法,它试图使用决策树集合将异常从其余数据中隔离出来。它不依赖于在标记数据上训练模型。
该方法选择一个要素,并在最小值和最大值之间随机分割数据。然后,该过程沿着决策树继续进行,直到数据中所有可能的拆分都已完成,或者达到拆分数量的限制。
任何异常/异常值都将在流程的早期被分离出来,使其易于识别并与其余数据隔离。
用这种方法检测异常假设:
- 异常的存在是很小的
- 异常值不同于正常值
下图展示了一个非常简单的例子,它使用了一个变量——体积密度(RHOB)和一棵树。

测井数据隔离林示例。图片由作者提供。
由于我们使用的是随机拟合数据的树的集合(组),因此我们可以取出现异常值的树内深度的平均值,并对该数据点的“异常值”进行最终评分。
隔离林的优势
与传统的基于距离和密度的模型相比,隔离林有许多优势:
- 减少计算时间,因为异常情况可以尽早快速识别
- 可轻松扩展到高维度和大型数据集
- 在一定程度上对数据进行子采样,这是其他方法无法做到的
- 包含不相关的功能时有效
隔离林 Python 教程
在下面的例子中,我们将看到如何用 seaborn 增强散点图。
数据源
为此,我们将使用一个更大数据集的子集,该数据集曾被用作 Xeek 和 FORCE 2020 (Bormann 等人,2020) 举办的机器学习竞赛的一部分。它是在挪威政府的 NOLD 2.0 许可下发布的,详细信息可以在这里找到:挪威开放政府数据许可(NLOD) 2.0 。
完整的数据集可以通过以下链接获得:【https://doi.org/10.5281/zenodo.4351155。
本文中的所有示例都可以用于任何数据集。
导入库和数据
对于本教程,我们将需要从 Scitkit-Learn 导入 seaborn 、 pandas 和 IsolationForest。
导入这些数据后,我们接下来需要加载我们的测井数据。在本例中,数据存储在一个 CSV 文件中,包含单井的测量值:15/9–15。
如果您不熟悉这种类型的数据,请不要担心,本教程中展示的技术同样可以应用于其他数据集。
这将返回以下数据帧摘要:

上面的总结只显示了文件中的数字数据。如果我们想要查看数据帧中的所有特性,我们可以调用df.info(),这将通知我们有 12 列数据,以及不同级别的完整性。

数据框内的特征概述。图片由作者提供。
与许多机器学习算法一样,我们需要处理丢失的值。如上所示,我们有几列,如 NPHI(中子孔隙度)有 13,346 个值,伽马射线有 17,717 个值。
处理这些缺失值的最简单方法是删除它们。尽管这是一个快速的方法,但也不应该盲目地去做,你应该尝试去理解丢失值的原因。在构建机器学习模型时,删除这些行会导致数据集减少。
如果你想了解更多关于处理缺失数据的信息,我建议你看看我的这些文章:
要删除丢失的行,我们可以调用:
如果我们再次调用df,我们将会看到每一列的值减少到了 13,290。

移除空值后数据帧中的要素概述。图片由作者提供。
用 Scikit-Learn 构建隔离林模型
从我们的数据框架中,我们需要选择用于训练隔离森林模型的变量。
在这个例子中,我将只使用两个变量(NPHI 和 RHOB)。实际上,我们会使用更多,稍后我们会看到一个例子。使用两个变量可以让我们看到算法做了什么。
首先,我们将创建一个列名列表:
接下来,我们将创建隔离林模型的一个实例。这是通过首先创建一个名为model_IF的变量,然后将它赋给IsolationForest()来完成的。
然后我们可以为我们的模型传入一些参数。我在下面的代码中使用的是:
- 污染:这是我们期望被视为异常值的总数据量。我们可以传入一个 0 到 0.5 之间的值,或者将其设置为 auto。
- random_state :这让我们可以控制分裂树的随机选择过程。换句话说,如果我们用相同的数据和参数重新运行这个模型,并且这个参数的值是固定的,那么我们应该得到可重复的输出。
一旦我们的模型被初始化,我们就可以用数据来训练它。为此,我们调用.fit()函数,并将其传递给我们的 dataframe ( df)。当我们传递 dataframe 参数时,我们还将选择我们之前定义的列。
拟合模型后,我们现在可以创建一些预测。为此,我们将在数据框架中添加两个新列:
anomaly_scores:通过调用model_IF.decision_function()生成,提供数据集中每个样本的异常分值。分数越低,样本越不正常。负值表示样本是异常值。anomaly:调用model_IF.predict()生成,用于标识一个点是离群点(-1) 还是内界点(1)
一旦发现异常,我们就可以查看我们的数据框架并看到结果。

显示异常检测结果的熊猫数据框。值为 1 表示数据点是好的。图片由作者提供。
在上面的返回值中,我们可以看到原始输入要素、生成的异常得分以及该点是否为异常。
使用 matplotlib 可视化异常数据
查看数值并尝试确定该点是否已被识别为异常值可能会很乏味。
相反,我们可以使用 seaborn 生成一个基本图形。我们可以使用我们用来训练模型的数据,并在视觉上将它分成离群值或内联值。
这个简单的函数旨在生成图表,并以文本形式提供一些额外的指标。该函数采用:
data:包含数值的数据帧outlier_method_name:我们正在使用的方法的名称。这只是为了展示的目的xvar、yvar:我们要分别绘制在 x 轴和 y 轴上的变量xaxis_limits、yaxis_limits:x 轴和 y 轴范围
一旦定义了函数,我们就可以传入所需的参数。
这产生了下面的图。

Seaborn 散点图显示了隔离林模型确定的异常值和内部值。图片由作者提供。
我们可以马上知道有多少值被识别为异常值,以及它们位于何处。由于我们只使用了两个变量,我们可以看到,我们基本上已经在数据边缘的点和数据中心的点之间形成了分离。
增加隔离林污染值
前面的示例使用 0.1 (10%)的值作为污染参数,如果我们将该值增加到 0.3 (30%)会怎么样?
当我们运行上面的代码时,我们得到了下面的图。我们可以看到更多的点被选择并识别为异常值。

Seaborn 散点图显示了隔离森林模型确定的 30%污染的异常值和内部值。图片由作者提供。
我们如何知道设置哪个污染值?
通过设置污染值,我们可以确定哪些百分比的值应该被识别为异常值,但选择该值可能会很棘手。
选择这个值没有严格的规则,它应该基于围绕数据及其预期应用程序的领域知识。
对于这个我非常熟悉的数据集,我会考虑其他特征,如井径和δ-rho(DRHO ),以帮助识别潜在的不良数据。
对隔离林使用 2 个以上的功能
现在,我们已经看到了使用只有两个变量的隔离林的基础,让我们看看当我们使用更多的变量时会发生什么。

Seaborn 散点图显示了隔离森林模型使用多个输入要素和 10%污染确定的异常值和内界值。图片由作者提供。
我们现在看到,被识别为异常值的点在散点图上更加分散,并且在一组核心点周围没有硬边界。
用 Seaborn 的配对图显示异常值
我们可以看看我们用过的所有变量,而不是只看两个变量。这是通过使用 seaborn pairplot 来完成的。
首先,我们需要设置调色板,这将允许我们控制在情节中使用的颜色。
然后,我们可以调用sns.pairplot并传入所需的参数。
它返回:

隔离森林后数据集中所有要素的 Seaborn pairplot。橙色点表示异常值(-1),蓝色点表示内部值(1)。图片由作者提供。
这为我们提供了一个更好的数据概览,我们现在可以清楚地看到其他特性中突出显示的一些异常值。尤其是在 PEF 和 GR 特性中。
摘要
隔离森林是一种易于使用和理解的无监督机器学习方法,可以将异常数据点从良好数据中隔离出来。如果需要,该算法可以扩展到处理大型和高维数据集。
如果您有兴趣了解这种方法与其他方法的比较,您可能会喜欢下面的文章:
**** ****
感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!或者,您也可以 注册我的简讯 免费将更多内容直接发送到您的收件箱。****
其次,通过注册会员,你可以获得完整的媒介体验,并支持我和其他成千上万的作家。每月只需花费你 5 美元,你就可以接触到所有精彩的媒体文章,也有机会通过写作赚钱。如果你用 我的链接报名,你直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!****
参考
博尔曼,彼得,奥桑德,彼得,迪里布,法哈德,曼拉尔,投降,&迪辛顿,彼得。(2020).机器学习竞赛 FORCE 2020 井测井和岩相数据集[数据集]。芝诺多。http://doi.org/10.5281/zenodo.4351156
这一切都归结于设计模式
原文:https://towardsdatascience.com/it-all-comes-down-to-design-patterns-c7034eb39ef9
通过软件设计模式发现真善美

作者图片
虽然软件架构不是关于编码的,但是作为一个软件架构师,你仍然需要有大量关于开发的知识,特别是设计模式。
为什么在你的 IT 工具箱下添加设计模式将有益于你的职业发展有许多原因,但是三个最重要的原因是:
- 设计模式在你使用的任何库、包和框架中都很普遍。如果你理解了一个模式的用法和它的基本原理,你将获得更深的洞察力,这将允许你更快地在许多语言中导航。
- 与开发团队拥有一个通用词汇表可以让你更容易、更准确地传达你的设计规范。
- 能够根据对象之间的交互来思考有助于设计一个能够经受住时间考验的抽象解决方案。
一切都是设计好的;很少有东西是设计好的!
诚然,我没有进行任何科学研究来评估哪些是最常用的模式。这是基于我作为开发人员和架构师在这个领域的经验观察。我们将首先钻研一些关于实体设计的基础知识,然后我们将探索我最重视的一组模式。
坚实的原则
我们之前已经看到了坚实的原则如何应用于架构本身,但是这里我们将关注它们在软件工程设计中的重要性。我们只会玩弄它们(也就是说,不会提供任何代码),但是对于每个想要了解他们的开发团队想要做什么的架构师来说,这将是一个很好的起点。
先说个定义……正如鲍勃大叔所说:它们不是法律。它们不是完美的真理。这些陈述的顺序是:一天一个苹果,医生远离我。这意味着它们不是某种“魔法”,不会带来牛奶、蜂蜜和伟大软件的乐土,但它们仍然是健壮和持久软件的重要贡献者。
简而言之,这些原则围绕着两个主要概念,它们是成功的企业应用程序的构建块:耦合是一个类了解另一个类并与之交互的程度,而内聚表示一个类具有单一目的的程度。换句话说:
耦合是关于类之间如何交互的,而内聚则关注单个类是如何设计的。
固体代表以下术语:
➊ — 单一责任原则
一个类应该有且只有一个理由改变。
这是不言自明的,但是说起来容易做起来难——向现有的类中添加新的行为总是很诱人的,但是这是灾难的一个处方:每个行为都可能是将来改变的原因,所以更少的行为导致在改变期间引入错误的机会更少。
➋ — 开闭原理
你应该能够扩展一个类的行为,而不用修改它。
您使用的类应该对扩展开放,但对修改关闭。实现这一点的一种方法是通过继承,即创建一个子类,这样原始类就不会被修改,但自定义代码会被添加到子类中以引入新的行为。
➌ — 利斯科夫替代原理
派生类必须可以替换其基类。
当将类 A 的行为扩展到子类 B 时,你必须确保你仍然可以在不破坏任何东西的情况下与 B 交换 A。这可能有点吸引人,尤其是当把这一原则与开闭原则结合起来时。
➍ — 界面偏析原理
制作客户特定的细粒度接口。
接口和类必须尽可能的专门化,这样调用客户端就不会依赖于它们不使用的方法。这与单一责任原则是相辅相成的。
➎ — 依存倒置原则
依靠抽象,而不是具体。
高级类不应该依赖于低级类。它们都应该依赖于抽象。同样,抽象不应该依赖于细节。细节应该依赖于抽象。
设计模式
四位作者(Gamma,Helm,Johnson & Vlissides)在 1994 年出版了著名的著作《设计模式:可重用的面向对象软件的元素》,其中包括 23 种面向对象的设计模式。由于它的名字很长,人们开始称它为“四人帮(g of)的书”。
从定义开始:
软件设计模式是对软件工程中常见问题的优化的、可重复的解决方案。这是一个解决问题的模板,可以在许多不同的情况下使用。
设计模式不是规定性的,而是有目的地保持高层次。他们的目标是提高代码质量,并最终坚持坚实的原则。他们也遵守这个规则:1。界面设计,2。偏好组合而非继承;3 .封装任何可变性。
作为架构师,首先要知道的是基于设计模式的意图或目的的不同类别,概括如下:
- 创建:它们提供对象实例化机制。
- 结构:他们专注于将物体组合成更大的结构。
- 行为:他们负责有效的沟通和跨对象的责任分配。
下图显示了我们将要探索的 GoF 模式:

最常见的设计模式
➊——建筑|创意
构建器模式旨在以一步一步的方式构建一个复杂的对象,并将构建与其表示分离开来。本质上,它允许使用相同的代码产生不同类型和表示的对象。
—用法说明:几种复杂的物体可以用相同的整体构建过程来构建,尽管各个构建步骤有所不同。

构建器模式
➋ —工厂方法|创新
工厂方法为创建对象定义了一个接口,但是实例化是由子类完成的。
—用法说明:事先不知道对象的确切类型和依赖关系。

工厂方法
➌——抽象工厂|创意
抽象工厂捕获如何创建相关对象的系列,而不指定它们的具体类。
—用法说明:存在不同的情况,需要不同的规则集实现,这些情况要么是事先未知的,要么是可扩展性问题。
⭐️ 与抽象方法的区别:
抽象工厂:创建其他工厂,这些工厂又创建从基类派生的对象。【工厂方法:创建从特定基类派生的对象。

抽象工厂
➍ —装饰|结构
装饰模式通过将对象放在包含这些行为的特殊包装类中,动态地将新的责任附加到对象上,因此对原始方法的签名没有影响(组合优于继承)。
—用法说明:在运行时给对象分配额外的行为,而不破坏使用这些对象的代码。

装饰图案
➎——立面|结构
Facade 模式为库、api、框架或任何其他复杂的类集提供了一个简化的接口。它实际上是一个复杂的从属子系统的包装器。
—用法说明:通过将子系统封装在单个接口对象中,将子系统与其潜在客户分离,该接口对象成为该系统的唯一接入点/网关。

立面图案
➎ —存储库
存储库模式不是 GoF 模式的一部分,但是它是一个特定于数据的 facade 实现,所以在这里值得一提。它解决了数据检索和持久化的代码集中化问题,并为数据访问操作提供了抽象,即类似于内存中的域对象集合,允许执行 CRUD 方法,并消除了任何数据库问题。
—用法说明:将业务逻辑与数据访问代码解耦。

知识库模式
➏ — 战略|行为
策略模式定义了一系列算法,将每个算法放在一个单独的类中,并使它们可以互换。将行为封装在单独的类中,消除了任何条件语句,并在运行时选择正确的算法(即策略)。
—用法说明:一个业务规则有不同的实现,或者需要不同的算法变体。

战略模式
➐ —模板法|行为
模板方法旨在从不同的过程中抽象出一个共同的过程。它定义了算法的框架,将一些步骤推迟到子类。子类可以覆盖一些行为,但不能改变框架。
—用法说明:有一组一致的步骤可以遵循,但各个步骤可能有不同的实现方式。
⭐️ 与策略模式的差异:
模板:通过子类在编译时选择算法。策略:由容器在运行时*选择算法。*

模板方法
➑ —责任链|行为
责任链模式建议通过启用一个或多个处理程序来满足请求,从而避免客户端(请求的发送者)与接收者之间的耦合。这些处理程序链接成一个链,即每个处理程序都有一个对链中下一个处理程序的引用。

—用法说明:一个以上的对象可能会处理一个请求,而处理程序(也不是序列)事先并不知道。

责任链
➒ —观察家|行为
Observer 模式(也称为 Publish/Subscribe 或 PubSub)通过定义对象之间的一对多依赖关系,实现了简单的广播通信,因此当一个对象经历状态变化时,它的所有依赖对象都会得到通知并自动更新。观察者有责任记录他们正在“观察”的事件。
—用法说明:当一个对象的改变需要改变其他对象,而你不知道需要改变多少个对象时。

观察者模式
➓ —命令|行为
Command 模式通过将两个类之间的请求转换成独立的对象来处理它们,这些对象封装了关于请求的所有信息,包括数据、方法参数、业务逻辑等。
—用法说明:在不知道被请求操作的内部或者请求的接收者的情况下,需要向对象发出请求。
⭐️ 注意:责任链模式可以使用命令将请求表示为对象。

命令模式
脚注
在这篇文章中,我们仅仅触及了一些设计模式的表面,但是我希望它能让你对构建软件的贡献因素有一个很好的了解,并且提升所有的能力:可扩展性、可维护性和可测试性。让应用程序设计对变化具有弹性是交付成功解决方案的关键。
好的设计显而易见。伟大的设计是透明的。
感谢阅读!
*我经常在媒体上写关于领导力、技术&的数据——如果你想阅读我未来的帖子,请*‘关注’我 !
*https://semika.medium.com/subscribe *
解释了可迭代、有序、可变和可散列的 Python 对象
讨论这些术语的真正含义和暗示,它们的主要细微差别,以及一些有用的变通方法

来自 Pixabay
可迭代、有序、可变和可散列(以及它们的反义词)是描述 Python 对象或数据类型的特征。尽管经常使用,这些术语经常被混淆或误解。在本文中,我们将讨论每个属性的真正含义和暗示,它们与什么数据类型相关,这些属性的主要细微差别,以及一些有用的解决方法。
可迭代的
Python 中的 iterable 对象是这样一种对象,它可以通过循环来逐个提取其项目,或者对每个项目应用特定的操作并返回结果。可迭代对象大多是表示一组项目(列表、元组、集、冷冻集、字典、范围和迭代器)的复合对象,但字符串也是可迭代的。
对于所有可迭代的数据类型,我们可以使用 For 循环来迭代对象:
for i in (1, 2, 3):
print(i)**Output:**
1
2
3
对于 Python 字典,默认情况下对字典键执行迭代:
dct = {'a': 1, 'b': 2}
print('Dictionary:', dct)print('Iterating over the dictionary keys:')
for i in dct:
print(i)**Output:**
Dictionary: {'a': 1, 'b': 2}
Iterating over the dictionary keys:
a
b
如果我们想要迭代字典值,我们必须在字典上使用values()方法:
print('Iterating over the dictionary values:')
for i in dct.values():
print(i)**Output:** Iterating over the dictionary values:
1
2
相反,如果我们想要迭代字典键和值,我们应该使用items()方法:
print('Iterating over the dictionary keys and values:')
for k, v in dct.items():
print(k, v)**Output:** Iterating over the dictionary keys and values:
a 1
b 2
所有可迭代的 Python 对象都有__iter__属性。因此,检查 Python 对象是否可迭代的最简单方法是对其使用hasattr()方法,检查__iter__属性是否可用:
print(hasattr(3.14, '__iter__'))
print(hasattr('pi', '__iter__'))**Output:** False
True
我们可以使用iter()函数从任何可迭代的 Python 对象中获得迭代器对象:
lst = [1, 2, 3]
print(type(lst))iter_obj = iter(lst)
print(type(iter_obj))**Output:** <class 'list'>
<class 'list_iterator'>
完全符合预期(有点赘述),迭代器对象是可迭代的,所以我们可以对它进行迭代。然而与所有其他可迭代对象不同,迭代器对象会在迭代过程中一个接一个地丢失元素:
lst = [1, 2, 3]
iter_obj = iter(lst)
print('The iterator object length before iteration:')
print(len(list(iter_obj)))for i in iter_obj:
pass
print('The iterator object length after iteration:')
print(len(list(iter_obj)))**Output:** The iterator object length before iteration:
3
The iterator object length after iteration:
0
注意,没有像其他可迭代数据类型的内置函数len()那样直接检查迭代器对象长度的方法。因此,为了检查迭代器中的项数,我们首先必须将它转换成另一个可迭代对象,比如一个列表或元组,然后才在其上应用len()函数:len(list(iter_obj))。
有序与无序
Python 中的有序对象是那些 iterable 对象,其中的项目保持确定的顺序,除非我们有意更新这些对象(插入新项目、删除项目、排序项目)。有序对象是字符串、列表、元组、范围和字典(从 Python 3.7+开始)、无序集、冷冻集和字典(在 Python 3.7 之前的版本中)。
lst = ['a', 'b', 'c', 'd']
s = {'a', 'b', 'c', 'd'}
print(lst)
print(s)**Output:** ['a', 'b', 'c', 'd']
{'b', 'c', 'a', 'd'}
由于有序对象中的顺序被保留,我们可以通过索引或切片来访问和修改对象的项目:
lst = ['a', 'b', 'c', 'd']# Access the 1st item of the list.
print(lst[0])# Access the 2nd and 3rd items of the list.
print(lst[1:3])# Modify the 1st item.
lst[0] = 'A'
print(lst[0])**Output:** a
['b', 'c']
A
为了从 Python 字典中提取特定的值,我们通常使用相应的键名称,而不是字典中的键索引:
dct = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
print(dct['b'])**Output:** 2
然而,如果出于某种原因,我们需要提取字典中已知位置的键的值(假设我们只知道的位置,而不知道键本身),或者字典中定义了位置范围的一部分键的一组值,我们在技术上仍然可以做到:
dct = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}# Access the value of the 1st key in the dictionary.
print(list(dct.values())[0])# Access the values of the 2nd to 4th keys in the dictionary.
print(list(dct.values())[1:4])# Access the 2nd to 4th keys in the dictionary.
print(list(dct.keys())[1:4])**Output:** 1
[2, 3, 4]
['b', 'c', 'd']
上面的解决方法不是很直接。但是,它有助于通过键或值来索引 Python 字典。
在无序对象中,我们不能访问单个项目:
s = {'a', 'b', 'c', 'd'}
print(s[0])**Output:
---------------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**~\AppData\Local\Temp/ipykernel_9980/849534030.py** in <module>
1 s **=** **{'a',** **'b',** **'c',** **'d'}**
**----> 2** print**(**s**[0])**
**TypeError**: 'set' object is not subscriptable
如果我们有一个包含其他复合对象的复合有序对象,我们可以更深入地挖掘并访问该对象项目的内部项目,如果它们也是有序的。例如,如果一个 Python 列表包含另一个 Python 列表,我们可以访问内部列表的项目:
lst_2 = [[1, 2, 3], {1, 2, 3}, 10]
print(lst_2[0][2])**Output:** 3
然而,我们不能访问列表中集合的项目,因为 Python 集合是无序的:
print(lst_2[1][2])**Output:
---------------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**~\AppData\Local\Temp/ipykernel_9980/895995558.py** in <module>
**----> 1** print**(**lst_2**[1][2])**
**TypeError**: 'set' object is not subscriptable
可变与不可变
Python 中的可变对象是那些可以被修改的对象。可变性并不一定意味着能够通过索引或切片来访问复合对象的单个项目。例如,一个 Python 集合是无序的和无索引的,然而,它是一个可变的数据类型,因为我们可以通过添加新的条目或从中删除条目来修改它。
另一方面,Python tuple 是一种不可变的数据类型,但是我们可以通过索引和切片轻松地访问它的各个项(但是不能修改它们)。此外,还可以对 range 对象进行索引和切片,从中提取整数或更小的范围。
一般来说,可变数据类型是列表、字典、集合和字节数组,而不可变数据类型是所有原始数据类型(字符串、整数、浮点、复杂、布尔、字节)、范围、元组和冻结集。
让我们探讨一个关于元组的有趣警告。作为不可变的数据类型,元组可以包含可变数据类型的项目,例如列表:
tpl = ([1, 2], 'a', 'b')
print(tpl)**Output:** ([1, 2], 'a', 'b')
上面的元组包含一个列表作为它的第一项。我们可以访问它,但不能为此项目重新分配其他值:
# Access the 1st item of the tuple.
print(tpl[0])# Try to re-assign a new value to the 1st item of the tuple.
tpl[0] = 1**Output:** [1, 2]**---------------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**~\AppData\Local\Temp/ipykernel_9980/4141878083.py** in <module>
3
4 **# Trying to modify the first item of the tuple**
**----> 5** tpl**[0]** **=** **1**
**TypeError**: 'tuple' object does not support item assignment
然而,由于 Python 列表是可变的有序数据类型,我们既可以访问它的任何项,也可以修改它们:
# Access the 1st item of the list.
print(tpl[0][0])# Modify the 1st item of the list.
tpl[0][0] = 10**Output:** 1
结果,我们的元组中的一个项目被改变了,并且元组本身看起来不同于最初的那个:
print(tpl)**Output:** ([10, 2], 'a', 'b')
可散列与不可散列
可散列 Python 对象是任何具有散列值的对象,散列值是该对象的一个整数标识符,在其生命周期中不会改变。为了检查一个对象是否是可散列的,并找出它的散列值(如果它是可散列的),我们在这个对象上使用了hash()函数:
print(hash(3.14))**Output:** 322818021289917443
如果对象不可修复,将抛出一个TypeError:
print(hash([1, 2]))**Output:
---------------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**~\AppData\Local\Temp/ipykernel_9980/3864786969.py** in <module>
**----> 1** hash**([1,** **2])**
**TypeError**: unhashable type: 'list'
几乎所有不可变的对象都是可散列的(我们很快会看到一个特殊的例外),而不是所有可散列的对象都是不可变的。特别是,所有的原始数据类型(字符串、整数、浮点、复数、布尔、字节)、范围、冷冻集、函数和类,无论是内置的还是用户定义的,都是可散列的,而列表、字典、集合和字节数组是不可散列的。
一个奇怪的例子是 Python 元组。因为它是不可变的数据类型,所以应该是可散列的。事实上,似乎是这样的:
print(hash((1, 2, 3)))**Output:** 529344067295497451
还要注意,如果我们将这个元组赋给一个变量,然后对该变量运行hash()函数,我们将获得相同的哈希值:
a_tuple = (1, 2, 3)
print(hash(a_tuple))**Output:** 529344067295497451
如果一个元组包含可变项,就像我们在上一节中看到的那样,会怎么样呢?
tpl = ([1, 2], 'a', 'b')
print(hash(tpl))**Output:
---------------------------------------------------------------------------**
**TypeError** Traceback (most recent call last)
**~\AppData\Local\Temp/ipykernel_8088/3629296315.py** in <module>
1 tpl **=** **([1,** **2],** **'a',** **'b')**
**----> 2** print**(**hash**(**tpl**))**
**TypeError**: unhashable type: 'list'
我们看到 Python 元组可以是可散列的,也可以是不可散列的。只有当它们包含至少一个可变项时,它们才是不可取消的。
有趣的是,即使是不可修复的元组,如上图所示,也有__hash__属性:
# Check if a hashable tuple has the '__hash__' attribute.
print(hasattr((1, 2, 3), '__hash__'))# Check if an unhashable tuple has the '__hash__' attribute.
print(hasattr(([1, 2], 'a', 'b'), '__hash__'))**Output:** True
True
我们前面提到过,并不是所有的可散列对象都是不可变的。这种情况的一个例子是可变但可散列的用户定义类。此外,类的所有实例都是可散列的,并且具有与类本身相同的散列值:
class MyClass:
passx = MyClassprint(hash(MyClass))
print(hash(x))**Output:** 170740243488
170740243488
通常,当两个 Python 对象相等时,它们的哈希值也相等:
# Check the equal objects and their hash values.
print(True==1)
print(hash(True)==hash(1))# Check the unequal objects and their hash values.
print('god'=='dog')
print(hash('god')==hash('dog'))**Output:** True
True
False
False
结论
总之,我们从多个方面详细探讨了经常使用但经常被误解的 Python 对象和数据类型特征,如可迭代、有序、可变、可散列以及它们的对立面,包括一些特殊情况和例外。
感谢阅读!
你会发现这些文章也很有趣:
</16-underrated-pandas-series-methods-and-when-to-use-them-c696e17fbaa4> https://levelup.gitconnected.com/when-a-python-gotcha-leads-to-wrong-results-2447f379fdfe https://medium.com/geekculture/creating-toyplots-in-python-49de0bb27ec1





浙公网安备 33010602011771号