如何准备机器学习数据
原文:
towardsdatascience.com/how-to-prepare-data-for-machine-learning-eb9d9973832f
准备数据以进行建模是数据科学流程中最基本的第一步之一
·发表于 Towards Data Science ·8 分钟阅读·2023 年 4 月 19 日
–
图片由 John Moeses Bauan 提供 / Unsplash
训练预测模型要求我们的数据格式正确。我们不能将 .csv
文件直接输入模型,并期望它能够正确地进行泛化。
在本文中,我们将探讨如何准备机器学习数据,从数据准备流程开始,到将数据划分为训练集、验证集和测试集。
数据准备流程
流程是为了准备数据以供机器学习模型处理而执行的一系列步骤。流程可以根据你拥有的数据类型有所不同,但通常包括以下步骤:
数据收集:第一步是收集你想用来训练机器学习模型的数据。这些数据可以来自不同的来源,如 CSV 文件、数据库、API、物联网传感器等。
数据清理:一旦数据被收集,重要的是要通过删除缺失数据、纠正错误以及删除重复或无关的数据来清理数据。数据清理有助于提高用于训练机器学习模型的数据质量。
数据预处理:数据预处理包括数据规范化、数据标准化、分类变量编码和异常值处理。这些步骤有助于准备数据,使其能够被机器学习模型处理。
数据转换:数据转换包括降维、特征选择和新特征的创建。这些步骤有助于减少数据噪声,提高机器学习模型做出准确预测的能力。
管道一词也用于 Python 的 Scikit-learn 库中的一个编程对象。这允许我们指定数据处理步骤,以便于编码并减少错误的发生。
如果你想了解 Scikit-Learn 管道,可以阅读下面的文章
在数据科学和机器学习中,管道(pipeline)是一组顺序步骤,允许我们控制数据流动的…
medium.com](https://medium.com/mlearning-ai/how-to-use-sklearns-pipelines-to-optimize-your-analysis-b6cd91999be?source=post_page-----eb9d9973832f--------------------------------)
数据收集
从可靠且与您试图解决的问题相关的数据源中收集数据是很重要的。
例如,如果你想创建一个用于预测房价的机器学习模型,你需要收集包括房屋位置、房间数量、是否有花园、靠近公共交通等特征的数据。这些信息就像它们向我们人类提供房产价值一样,也会对预测模型提供帮助。
选择最适合你的问题和数据可用性的来源非常重要。例如,如果你想为物联网系统中的异常检测构建机器学习模型,你需要从系统中的传感器收集数据。
其他数据来源可以是
-
公共和私人 API
-
直接供应商
-
调查
此外,评估收集的数据质量是很重要的。数据可能包含错误、重复或遗漏,这些问题可能会对机器学习模型的质量产生负面影响。
因此,你应该进行数据清理,并检查是否有缺失、重复或不良数据。数据清理可以帮助提高机器学习模型的质量,并实现更准确的预测。
这里有一篇文章专注于数据集创建的过程
自建数据集而不是使用预建解决方案的重要性
towardsdatascience.com
数据收集阶段的实际例子
一个实际的数据收集例子可能是构建一个用于分类花卉的模型。在这种情况下,我们可以收集不同花卉的数据,比如花瓣和萼片,并记录它们的长度和宽度。
为了收集这些数据,我们可以使用一个移动应用程序,该程序允许我们拍摄花卉照片并记录它们的特征。或者,我们可以使用电子表格或数据库手动收集数据。
无论你使用什么数据收集方法,确保数据能代表要解决的问题是很重要的。在这种情况下,我们可以收集来自不同物种和不同地理区域的花卉数据,以确保数据的多样性。
数据预处理
一般来说,数据预处理包括数据的归一化或标准化、类别变量的编码和异常值处理。
数据归一化 / 标准化用于减少数据的尺度,以便它们之间可以相互比较。许多机器学习模型,例如 K-近邻和神经网络,要求数据进行归一化或标准化才能表现良好。
标准化和归一化这两个术语通常可以互换使用。但实际上,它们并不完全相同。
我邀请有兴趣的读者阅读这个资源解释标准化和归一化之间差异的文章以了解更多信息。
类别变量编码用于处理那些不是数字格式的变量,例如性别或眼睛颜色。类别变量编码将这些变量转换为数字,以供机器学习模型使用。
异常值处理用于处理那些与其余数据差异很大的数据。这些数据可能对机器学习模型产生负面影响,因此必须妥善管理。
数据预处理阶段的实际示例
让我们思考文本。大量的文本组织成语料库(文档列表)。在将这些数据输入机器学习模型之前,应该首先对其进行处理。
一个假设的流程可能是:
-
文本标记化
-
停用词移除
-
词干提取 / 词形还原
-
向量化
这些是文本数据预处理中的一些最常见步骤。
数据转换
数据转换可以包括维度减少、特征选择和新特征的创建。
维度减少用于减少数据中的特征数量。当你拥有大量数据但资源受限,例如机器学习模型的处理时间,这一步骤会非常有用。最常用的技术之一是PCA(主成分分析)。
特征选择用于选择数据中最重要的特征。当你拥有许多特征但希望只使用其中一部分来训练机器学习模型时,这一步骤会非常有用。
阅读如何在 Python 中使用一种叫做Boruta的技术进行特征选择
了解 Boruta 算法如何进行特征选择。解释 + 模板
[towardsdatascience.com
创建新特征用于从现有数据中创建新特征。这个步骤在你想要创建在原始数据中不存在但对训练机器学习模型有用的特征时特别有用。
数据转换阶段的实际示例
PCA 主要用于减少机器学习模型中的可用特征数量。
在 Sklearn 中实现 PCA 非常简单 —— 这段代码片段捕捉了训练和应用 PCA 的一般思路。
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_train)
X_train_pca = pca.transform(X_train)
# we have successfully reduced the entire feature set to just two variables
x0 = X_train_pca[:, 0]
x1 = X_train_pca[:, 1]
将数据划分为训练集、验证集和测试集
将数据划分为训练集、验证集和测试集是准备机器学习数据的重要步骤。
训练集:训练集用于训练机器学习模型。它包含模型将用来学习对预测有用的关系的数据。
验证集:验证集用于在训练过程中评估机器学习模型的性能,并测试其超参数。
测试集:测试集用于在训练后评估机器学习模型的性能。
将数据划分为训练集、验证集和测试集很重要,因为它可以准确评估机器学习模型的性能。
如果你使用所有数据来训练机器学习模型,然后在数据本身上评估模型性能,你可能会得到模型性能的扭曲图景。
正确评估模型是否学习到知识的最重要技术之一叫做交叉验证。
它包括将训练集划分为折叠,以便迭代地评估模型。
如果你想了解交叉验证及其如何在你的代码中应用,阅读下面的文章。
了解什么是交叉验证 —— 构建可泛化模型的基本技术。
[towardsdatascience.com
使用 Sklearn 的 train_test_split
的实际示例
Sklearn 提供了一个很棒的 API 来管理数据拆分。看看这段代码。
from sklearn.model_selection import train_test_split
# X is out feature set, y is the target column
X_trainval, X_test, y_trainval, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(X_trainval, y_trainval, test_size=0.25, random_state=42)
print(f"Training set shape: {X_train.shape}")
print(f"Validation set shape: {X_val.shape}")
print(f"Test set shape: {X_test.shape}")
在这个示例中,我们将数据按 60/20/20 的比例划分为训练集、验证集和测试集。换句话说,我们使用 60% 的数据来训练模型,20% 来验证模型在训练过程中的表现,其余的 20% 用于在训练后测试模型。
random_state
参数允许我们设置随机数生成种子,以便每次运行代码时都能获得相同的数据划分。
最终,我们打印了训练集、验证集和测试集的大小,以便验证数据划分是否正确。
使用 sklearn
,将数据分为训练集、验证集和测试集变得快速而简单,使我们能够专注于设计和训练模型。
结论
在这篇文章中,我们查看了为机器学习准备数据所需的步骤。
我们已经看到,从可靠且与问题相关的来源收集数据以及数据清洗是确保用于训练机器学习模型的数据质量的关键。
我们还研究了数据预处理,包括数据归一化和标准化、分类变量编码和异常值处理,以及数据转换,这可能包括降维、特征选择和新特征的创建。
最终,我们了解了将数据分为训练集、验证集和测试集以准确评估机器学习模型性能的重要性。
为机器学习准备数据需要密切关注细节,但正确执行数据准备步骤可能是机器学习模型有效与否的关键。
如果你想支持我的内容创作活动,请随时通过下面的推荐链接加入 Medium 会员计划。我将获得您投资的一部分,您将能够以无缝的方式访问 Medium 上大量关于数据科学及更多内容的文章。
[## 使用我的推荐链接加入 Medium - Andrea D’Agostino
阅读 Andrea D’Agostino 的所有故事(以及 Medium 上成千上万的其他作者的故事)。您的会员费用直接……
medium.com](https://medium.com/@theDrewDag/membership?source=post_page-----eb9d9973832f--------------------------------)
推荐阅读
对有兴趣的人,这里列出了我推荐的每个与机器学习相关的主题的书籍。这些书籍在我看来是必读的,并且对我的职业生涯产生了很大影响。
免责声明:这些是亚马逊会员链接。我会因推荐这些商品而从亚马逊获得少量佣金。您的体验不会改变,也不会额外收费,但这将帮助我扩大业务并制作更多关于人工智能的内容。
-
机器学习入门: 自信的数据技能:掌握数据工作基础,提升你的职业生涯 作者:Kirill Eremenko
-
Sklearn / TensorFlow: 动手实践机器学习:Scikit-Learn、Keras 和 TensorFlow 作者:Aurelien Géron
-
NLP: 文本作为数据:机器学习与社会科学的新框架 作者:Justin Grimmer
-
Sklearn / PyTorch: 使用 PyTorch 和 Scikit-Learn 进行机器学习:用 Python 开发机器学习和深度学习模型 由 Sebastian Raschka 著
-
数据可视化: 用数据讲故事:商业专业人士的数据可视化指南 由 Cole Knaflic 著
有用的链接(由我编写)
-
学习如何在 Python 中进行顶级的探索性数据分析: Python 中的探索性数据分析 — 一步一步的过程
-
学习 TensorFlow 基础知识: 开始使用 TensorFlow 2.0 — 深度学习简介
-
使用 TF-IDF 在 Python 中进行文本聚类: Python 中的 TF-IDF 文本聚类
如何准备你的数据以进行可视化
Python
无需使用 Tableau Prep 或 Alteryx
·
关注 发布于 Towards Data Science ·8 分钟阅读·2023 年 6 月 26 日
–
图片由 Robert Katzki 拍摄,来源于 Unsplash
想开始你的下一个数据可视化项目吗?首先从熟悉数据清洗开始。数据清洗是任何数据管道中至关重要的一步,将原始的“脏”数据输入转变为更可靠、更相关、更简洁的数据。诸如 Tableau Prep 或 Alteryx 之类的数据准备工具是为此目的而创建的,但为什么要花钱使用这些服务,而你可以通过像 Python 这样的开源编程语言完成这项任务呢?本文将指导你如何使用 Python 脚本为可视化准备数据,提供一种比数据准备工具更具成本效益的替代方案。
注:在本文中,我们将重点关注使数据为 Tableau 的数据可视化做好准备,但主要概念同样适用于其他商业智能工具。
我明白了。数据清洗似乎只是将可视化或仪表板变为现实这一漫长过程中的另一个步骤。但它至关重要,并且可以是令人愉快的。这是你通过深入了解你拥有和没有的数据,以及你必须做出的相应决策,来使自己对数据集感到舒适的过程。
尽管 Tableau 是一个多功能的数据可视化工具,但有时获得答案的路线并不明确。这时,在将数据加载到 Tableau 之前处理数据集可能是你最大的秘密帮手。让我们探讨一些在将数据集成到 Tableau 之前,数据清洗为何有益的关键原因。
-
消除无关信息: 原始数据通常包含不必要或重复的信息,这可能会使你的分析变得杂乱。通过清洗数据,你可以去除这些垃圾,并将你的可视化集中在最相关的数据特征上。
-
简化数据转换: 如果你对打算生成的可视化有清晰的愿景,在将数据加载到 Tableau 之前进行这些预转换可以简化过程。
-
团队内部的更容易转移: 当数据源定期更新时,新增加的内容可能会引入不一致性,并可能破坏 Tableau。通过 Python 脚本和代码描述(更正式地称为 Markdown 文档),你可以有效地分享并帮助他人理解你的代码,以及排除可能出现的任何编程问题。
-
节省数据刷新时间: 需要定期刷新的数据可以从利用Hyper API中受益——这是一个生成特定于 Tableau 的 Hyper 文件格式的应用程序,并允许自动数据提取上传,同时使数据刷新过程更高效。
现在我们已经介绍了一些准备数据的优势,让我们通过创建一个简单的数据管道将其付诸实践。我们将探讨数据清洗和处理如何集成到工作流程中,并帮助使你的可视化更易于管理。
使用 Python 脚本创建数据管道
图片由作者提供
我们的数据旅程相当简单:数据清洗、数据处理以生成可视化,并将其转换为 Tableau 准备好的 Hyper 文件,以实现无缝集成。
在深入我们的工作示例之前,有一点需要注意的是,对于 Hyper 文件转换,你需要下载 pantab
库。这个库简化了 Pandas 数据框到 Tableau .hyper 提取文件的转换。你可以通过在你选择的环境的终端中使用以下代码轻松完成这个任务(对于那些对环境不太熟悉的人,这是一个很好的入门文章,介绍了环境是什么以及如何安装某些库):
#run the following line of code to install the pantab library in your environment
pip install pantab
教程:使用 Python 探索加拿大电动汽车许可证的数据准备
我们将致力于制作的数据可视化专注于根据来自加拿大统计局的政府数据,展示不同电动汽车制造商和车型的受欢迎程度。
需要注意的是,这建立在我之前文章中探讨的数据集基础上:使用 R 进行电动汽车分析。如果你对数据集的初步探索和决策背后的理由感兴趣,请参考该文章以获取更多细节。本教程专注于构建 Python 脚本,在每一步执行初始输入后,我们将把每个 Python 脚本的输出保存到其相应的文件夹中,如下所示:
图片由作者提供
文件夹流程确保管道组织良好,并且我们能够保留项目中每个输出的记录。让我们开始构建第一个 Python 脚本吧!
数据清洗
我们管道中的初始脚本遵循数据清洗的基本步骤,对于这个数据集包括:保留/重命名相关列,删除空值和/或重复项,以及使数据值一致。
我们可以通过指定输入文件的位置和输出文件的目的地来开始。这一步很重要,因为它使我们能够在相同位置组织文件的不同版本,在这种情况下,我们每月修改文件输出,因此每个文件输出按月份分隔,如文件名末尾所示 2023_04
:
以下代码读取原始 .csv 输入文件,并定义我们希望保留的列。在这种情况下,我们感兴趣的是保留与购买车型相关的信息,并忽略与汽车经销商或其他无关列相关的信息。
现在我们可以缩短列名,去除前导或尾随的空白,并添加下划线以便更易于理解。
接下来,在检查数据集中只有少量空值之后,我们将使用.dropna
函数删除这些空值。此时,你还可以删除重复项,但对于这个特定的数据集,我们将不这样做。这是因为存在大量重复信息,并且在缺乏行标识符的情况下,删除重复项会导致数据丢失。
最后一步是将我们的数据保存为 .csv 文件到适当的文件夹位置,该位置将放在我们共享目录的clean_data
文件夹中。
注意我们如何使用__file__
引用文件,并通过使用 bash 命令指定文件目录,其中../
表示上一级文件夹。这结束了我们的数据清理脚本。现在让我们进入数据处理阶段吧!
完整的工作代码和汇总脚本可以在我的 GitHub 仓库 这里 找到。
可视化的数据处理
让我们重新审视一下我们试图实现的可视化目标,这些目标旨在突出电动车注册受欢迎程度的变化。为了有效展示这一点,我们希望最终的 Tableau 准备数据集包含以下功能,我们将对此进行编码:
-
按年份的车辆绝对计数
-
按年份的车辆比例计数
-
注册车辆的最大增加和减少
-
注册车辆的排名
-
车辆注册的先前排名以便进行比较
根据你希望生成的可视化,创建理想列可能是一个迭代过程。在我的情况下,在构建了可视化之后我才包含了最后一列,因为我知道我想提供一个排名差异的视觉对比,因此 Python 脚本做了相应的调整。
对于以下代码,我们将重点关注模型聚合数据集,因为品牌的其他数据集非常相似。让我们首先定义我们的 inputfile
和 outputfile
:
注意我们如何引用来自 clean_data
文件夹的 inputfile
,这是我们数据清理脚本的输出。
以下代码读取数据,并创建一个按 Vehicle_Make_and_Model
和 Calendar_Year
聚合计数的数据框:
pivot
函数的功能类似于 Excel 中的数据透视表功能,它将 Calendar_Year
中的每个值作为列输入。
然后,脚本使用 For Loop 创建 per_1K
输入。这计算每个模型的比例,以便在相同的尺度上比较每个模型,并为每一年创建一列:
通过按年份计算比例,我们可以从数据集开始的 2019 年到最后一个完整数据年的 2022 年,计算每个模型的最大增加和减少。
在这里,melt
函数用于将按年份分离的 per_1K
列重新透视为行,以便我们只保留一个 per_1K
列及其相关值。
以下代码允许我们将绝对计数和刚刚创建的其他计算进行连接。
我们现在可以使用许可计数创建rank
列,并按Vehicle_Make_and_Model
和Calendar_Year
对这些值进行排序。
最后一列是通过使用shift
函数创建的previous_rank
列。
最后,我们可以将输出保存到管道中的clean_model
文件夹路径,提供一个准备好可视化的数据集。
作为友好的提醒,包括
clean_brand
处理数据集的完整 Python 脚本代码可以在我的 GitHub 仓库这里找到。
将最终数据文件转换为.hyper 文件格式
我们管道中的最后一步相对简单,因为我们只需将处理后的.csv 文件转换为.hyper 文件格式。只要你已经下载了前面提到的pantab
库,这应该是相对容易的。
值得一提的是,在 Tableau 中,连接的数据可以是实时连接或提取的。实时连接确保数据有持续流动,源的更新几乎立即在 Tableau 中反映出来。提取的数据涉及 Tableau 创建一个带有.hyper 文件扩展名的本地文件,其中包含数据的副本(数据源的详细描述可以在这里找到)。它的主要优点是加载速度快,Tableau 可以更高效地访问和呈现信息,这对于大型数据集特别有益。
hyper 文件转换脚本的代码从加载pandas
和pantab
包开始,然后读取你需要的cleaned_model
数据集以供 Tableau 使用。
最后一行代码使用frame_to_hyper
函数生成.hyper 文件并将其保存到hyper
文件夹中。
最后一步,我们可以通过打开一个新工作簿将.hyper 文件格式轻松加载到 Tableau 中,在select a file
部分你可以选择要加载的文件,选择more
。当我们加载ev_vehicle_models.hyper
文件时,它应显示为 Tableau 提取,如下图所示,你的数据已准备好进行可视化构建!
结束语
通过将深思熟虑的规划融入到你的可视化中,你可以通过创建简单的数据管道来简化仪表板的维护。如果你缺乏资源也不要担心;像 Python 这样的开源编程程序提供了强大的功能。作为最终的友好提醒,要获取 Python 脚本,请查看我的 GitHub 仓库这里。
除非另有说明,所有图片均由作者提供。
参考文献
-
Salesforce, Tableau Hyper API, 2023
-
R.Vickery, 数据科学家 Python 虚拟环境指南,2021 年 1 月
-
K.Flerlage, Tableau 数据源第一部分:数据源类型,2022 年 7 月
如何在软件工程师职位面试中展示你的项目
软件工程师面试的实用技巧
·
关注 发表在Towards Data Science · 7 分钟阅读·2023 年 6 月 16 日
–
展示技术项目是软件工程师职位面试中的常见部分,这用于测试你的技能和经验。
今天我想分享一些帮助我提升项目展示效果的技巧,希望对你有所帮助。*
开篇部分应当清晰明了
你的展示的第一段应非常直接,易于理解,并清楚表明要点。面试官应理解你的项目目的,以及你希望他们在深入具体细节之前记住的一个重要细节(或成就)。
为实现这一点,你的展示第一部分应包括以下内容:
-
一个清楚总结你项目的“标题”句子。
从核心内容开始。例如,“我将展示一个我在最近工作中进行的 3 个月项目,其中我从零开始构建了公司的主要数据增强系统。”
-
对公司的产品和/或团队的领域的介绍。
在深入项目之前,帮助面试官理解公司或团队领域的背景。
-
解释需求/痛点。
在开始讨论解决方案——即你的项目——之前,解释其必要性。项目之前的状况是什么?你的项目旨在解决哪些痛点?为什么你的项目很重要?
架构图
拥有一个包括项目组件、所用技术以及团队/公司周边架构的架构图,将使面试官更容易跟随,同时方便你引用特定组件或技术。
最好提前准备好图表,以免在面试过程中浪费时间。
在制作你的图表时请记住以下几点:
-
保持你的图表相对高层次且易于理解,避免过于详细的内容。记住你是在向一个不具备所有背景的外部听众解释。
-
确保你的图表包含足够的信息来传达相关细节,例如项目中使用的主要组件和技术。
-
考虑包括一些你们团队在日常工作中使用的组件和技术,即使它们在这个项目中没有被具体使用。这为你展示项目之后提供了潜在的讨论话题,展示额外的经验和知识。
此外,我倾向于为项目展示准备两个图表:一个提供公司产品或团队领域的高层次概述,另一个深入项目本身的解释。
从高层次图表开始可以减少面试官的负担,因为他们已经具备背景信息和对系统及产品的总体理解。
以下是我所提供的架构图示例:
高层次产品概述
作者草图
深入项目概述
作者草图
注意颜色——我所参与的系统部分在两个图表中均标记为蓝色。
通过在图表中包含比实际讨论更多的组件并突出我们将重点关注的部分,我实现了几个目标:
-
面试官一眼就能获得比我在展示中明确提到的更多信息,从而洞悉我所使用的技术范围。
-
我隐性地邀请面试官询问我关于项目架构的问题。
-
我让面试官很容易识别我在项目中工作的系统部分,并理解我的展示重点。
请记住,你的图表不仅限于远程面试。你还可以打印图表并带到办公室面试中。我个人可以证明,每次在面试中拿出我的图表时,面试官都对这种准备程度印象深刻。这是一种展示你专注和职业素养的非同寻常且有影响力的方法。
如果你无法携带这些视觉辅助工具,练习快速绘制重要部分,以保持绘图时间简短并让面试官保持关注。
成就 — 推而非拉
以下是一些值得提及的成就示例:
-
可衡量的成就: 将错误率降低 X%,将系统延迟提高 Y 毫秒或 Z%,减少每季度 W 天的手工工作。
-
克服障碍(这个项目为何复杂?) 例如,处理高度复杂的代码库或遗留代码,在低测试覆盖率或有限可见性的环境中调试,独立学习复杂的新技术和系统。
-
侦察规则: 展示你如何使环境变得比收到时更好,例如进行知识分享会议或实施重要的指标和警报。
-
人际事务 例如,与来自不同团队和部门的同事合作,有效地表达你的信息以吸引和引导他们。
记住,不同的面试官可能对你工作的不同方面有不同的兴趣,这取决于职位的需求或他们想了解的技能。
以 WOW 效果结束
从你之前列出的成就中,选择最令人印象深刻的几项纳入你的结尾段落。突出最令人印象深刻的数字或指标,展示你努力工作的回报。这些元素很可能会给面试官留下深刻的印象。
进行模拟面试并获取反馈
模拟面试是评估你的项目对外部听众的感觉、练习解释的连贯性和提升整体展示效果的好方法。它们提供了从客观和专业人士那里获取具体反馈的机会。
如果可能的话,尝试与熟悉你要展示项目的人员(前同事)和不熟悉的人员进行模拟面试。前者可以帮助发现任何不准确之处或提出更好的展示方式,而后者则可以从面试官的角度提供宝贵的反馈。
无论哪种情况,确保安排与在你领域工作的人员进行模拟面试——有经验的开发人员,最好是经验丰富的面试官,这样他们的反馈对你最为相关。
从一次面试中改进
图片由 Karolina Grabowska 提供,来源于 Pexels
将每次面试看作是一次小的回顾。面试后反思:
-
如果面试官误解了你的某个解释,考虑下次如何更清晰地表达。
-
如果在你结束展示后面试官反复问一些问题——考虑在下次面试中作为展示的一部分主动提供答案。
-
如果对某些回答不满意,找出改进这些回答的方法,并为未来的面试准备类似或相关的问题。
每一次面试都是学习和成长的机会。花时间分析和反思面试经历。如果你投入一些时间进行自我反思,可能会发现宝贵的见解。
总结
总结本文中提到的关键要点,以下是我对在软件工程师职位面试中展示项目的建议:
-
确保你的开场部分清晰明确。从一个概述项目目的的底线句子开始。然后,提供公司产品或团队领域的简短概述。最后,解释你的项目旨在解决的痛点,以结束介绍。
-
准备一个架构图,最好是两个:一个是产品的高层概览,另一个是项目的深入图。如果有面对面的面试,考虑打印这些图表或学习如何快速勾勒主要组件。
-
了解你在面试中想提及哪些成就,并以“主动推送而非被动等待”的方式分享。这意味着在项目展示过程中主动提及这些成就,而不是等到被问及时才说。
-
以 WOW 效果收尾,在你的展示结束时突出你最令人印象深刻的成就,特别是那些可以量化的成就,以在面试官心中留下持久的印象。
-
进行模拟面试并获取反馈。安排这些模拟面试与经验丰富的开发人员和面试官进行,以学习他们的见解和建议。
-
将每次面试视为学习机会,并在面试后花时间进行反思。反思这次经历,并将学到的经验应用到你未来的面试中。
就这些!希望你喜欢阅读这篇博客文章。你是否有任何额外的技巧来展示面试中的项目?在评论中分享你的想法吧!
2023 年如何为 AI 项目定价
我在 2017 年 TDS 文章发布 6 年后,新的变化和保持不变的内容
·
关注 发表在 数据科学的前沿 ·11 分钟阅读·2023 年 1 月 12 日
–
图片由 愚木混株 Cdd20 提供,来源于 Pixabay
这是一篇为从事 AI/ML 咨询领域的其他人提供难得见解的文章。几乎没有人愿意谈论定价或招聘实践。我 2017 年的文章“如何为 AI 项目定价”提供了如何为客户定价机器学习(ML)项目的见解。但是自 2017 年以来已经过去很久了!在提供 2023 年关于如何为 AI/ML 项目定价的更新之前,我将简要总结一下我之前文章的一些关键点。由于需求变更、集成问题、数据问题和用户接受等因素,为大型 ML 项目提供固定价格估算可能会很棘手。执行 ML 项目期间,管理层和项目经理应该预期需求和范围会发生变化。未知或需求风险越大,你应该越倾向于使用按小时计费而非固定价格。尽量在尽早的阶段消除风险。科学优先,工程其次。截止到 2017 年 8 月,我们的 ML 咨询标准费率是每小时 250 美元,项目必须至少为 5000 美元(20 小时)。我们在 2017 年的目标是在固定价格项目中也能达到我们的小时费率……但现在是 2023 年了。让我们看看发生了什么变化,什么保持不变。
一样的地方是什么?
令人惊讶的是,我们的展望与 2017 年时基本相同。自那篇文章发布以来已经过去了几年,这篇文章被各种来源引用,例如这篇和这篇。自 2017 年以来,人工智能咨询行业快速发展,我注意到一些事情保持不变。其中一个主要的稳定点是,疫情定价对我们来说并不存在。自 2017 年以来,我们保持了每小时 250 美元的定价。我们的固定定价基于这一小时费率,如 2017 年文章中所述,我们发现这种定价模式对客户和我们的业务都有效。此外,我们继续保持作为分包商的模式,而不是成为主要承包商,因为这使我们能够根据需要进行扩展或缩减,并且与政府客户的互动变得更加容易。然而,我们发现拥有一个行业特定的销售团队(如国防、产品、政府和金融科技)有助于我们为服务建立长期定价。
图片由Clker-Free-Vector-Images提供,来源于Pixabay
在定价方面,我们发现未知因素,即我们所称的需求风险,仍然是决定我们倾向于小时计费还是固定价格的主要因素。当需求不明确或可能发生变化时,我们更倾向于小时计费。另一方面,当需求非常紧密,或我们称之为可执行规格时,我们更倾向于固定价格。AI/ML 项目的一个大风险是数据的访问,这已成为我们在大大小小项目中看到的许多范围变化和延迟的源头。
我们发现,为项目定义和解决方案架构提供专家建议已成为我们咨询服务中越来越重要的一个方面。许多客户带着定义不明确的需求来找我们,我们必须在讨论的最早阶段识别任何证明无法实现的需求或需要新科学的需求。
此外,正如我们在 2017 年所做的,我们继续倾向于将尽可能多的低端工作委托给客户的员工,以减少完成项目所需的时间。
我们在 AI/ML 咨询领域的招聘情况
显然,给 AI 项目定价与薪酬密切相关。在疫情期间,AI/ML 咨询领域发生了几次变化,主要与人员配置有关。首先,大公司纷纷抢聘新手,支付不合理的薪水。这使得人才库枯竭,并为统计学和生物学等领域的新毕业生创造了一些扭曲的激励,促使他们转行进入数据科学和 ML 领域。这也导致了人员流动,因为员工在公司间跳槽。去年,情况再次发生变化。随着经济放缓和裁员的到来,我采访了许多被大型组织解雇并希望转向咨询的人员。客观地说,离开六位数薪水工作的人员,其资格比期待五位数薪水的新毕业生要低。不幸的是,这些所谓的经验丰富的候选人更不愿意付出必要的努力来展示他们的能力。我惊讶地发现,申请人的薪资预期与工作经验年限高度相关,但那些有约 3 到 5 年经验的申请人的测试分数显著下降。我的理论是,在大团队中从事狭窄任务几年后,许多经验丰富的申请人并不习惯我们每天所做的疯狂咨询工作。我的方法是忽略市场的波动,专注于个人为团队带来的价值。我尽量将这一点与候选人将带来的可计费工作紧密匹配。因此,定价与薪酬相关,通过确保薪酬和利润率让我们保持我们的定价结构。
让我告诉你一些关于我们招聘过程的事情。我们在这里的做法有些不同。我们询问每位候选人的薪资期望,然后在技术面试评分后,我们将薪资期望与技能测试得分绘制成图表。我们根据图表显示的最佳技能价值来选择雇用对象。我们从经过初筛的申请者那里请求薪资期望(技术、地域和人际评分),然后尽量回答他们可能对角色或公司有的任何问题,这些问题在招聘广告中没有说明。我的目标是在技术面试之前解决职责和薪资讨论,以便技术面试可以完全专注于技术能力。每个人的时间对我来说都很宝贵,因此我创建了一个在技术面试之前的每一步,人们可以以很少的时间投入申请,然后决定是否进入下一步或退出的情况。
有什么新鲜事?
首先,我们依然存在!这是一个大消息。在一个充满了要么退出要么消失的初创企业的领域中,我们保持了独立。我们根据我在 2017 年《Towards Data Science》文章中描述的基本计划,逐步建立了我们的业务和声誉。我们的 CEO Matt 于 2016 年 6 月 22 日成立了公司,而七年后我们依然在坚持我们的事业。
销售渠道随着时间的推移也发生了变化。我们一直提供详细的 RFQ,但值得一提的变化是我们现在申请了一些 RFI 和 RFP。我们曾经非常讨厌 RFP,以至于根本不申请它们。现在,随着时间的推移,我们已经在 供应商名单 上安下了我们的旗帜,当我们认为某个机会对我们来说足够有吸引力时,我们就会参与竞争。
致谢:作者以及那些通过坚定的意志熬夜填写表格使我们进入这些名单的工作人员。
就项目的规模而言,我们现在的目标是建立预期能够带来至少 100K(400 小时)规模的项目的关系。微小的客户不再那么适合。然而,如果我们看到一个初始范围为 25K,但有后续机会达到 150K 的项目,我们就会全力投入并建立起关系。请注意我们将重点从项目规模转变为客户规模。从一个客户那里获得的小交易逐渐累积,因此我们已将概念转变为代表客户而非具体交易。如果我们与一个合作伙伴合作处理各种小交易,我们可以汇总他们带来的项目的价值,这样这些较小的交易从“大局”角度来看就会变得有意义。至于我们如何建模业务,我们使用一个名为 floatapp.com 的工具,将我们的 P&L 数据直接输入到我们的收入预测中。我们还使用 HubSpot 作为 CRM 来跟踪交易和机会。
对于项目管理,我们使用一个 自托管的 GitLab 实例来进行大多数票据跟踪,但我们最终使用各种工具,因为我们使用客户希望我们在其项目中使用的工具(从 JIRA 到 GitHub)。我们现在明确收取项目管理费用,这是件好事。
另一个变化是我们现在提供给客户的产品种类更多了。我们的服务被组织为 SKU,包括 AI 战略、差距分析和 AI 路线图,交付物是报告或顾问服务、解决方案架构,交付物是需求文档或范围文档、虚拟 CTO,服务包括与高管团队和投资者的更深入互动,以及我们最初的技术产品,包括 数据科学、AI/ML/DL 模型训练 和 部署与 MLOps。这里的变化是我们在过程早期就介入(例如,路线图开发),并且我们会坚持更长时间(例如,MLOps 和带有服务水平协议的支持)。
多汁的本地硬件。感谢:Lemay.ai 和对在服务器室中增加额外电力线非常理解的电工。
我们还转向使用比以往更多的本地硬件。我们的硬件现在是 4U 机架式的,包括 Titan RTX(24 GB,4608 CUDA 核心,Turing 架构,CC 7.5)、5 个 RTX 3090(24 GB,10496 CUDA 核心,Ampere 架构,CC 8.6)配备 NVLINK、5 个 GeForce GTX 1050 Ti,以及大量 Xeon/i9 CPU,当然,还有大量的 RAM。当在本地进行研发活动时,我们可以节省很多费用。如果涉及到磁盘,我们一直使用 unraid 进行二级备份以及离线三级备份。顺便提一下,当你订购 45drives storinator server 时,可以将 unraid 安装为基础操作系统。另一个变化是,我们看到一些转向云计算的客户又转回了本地 GPU,并存储在托管设施中。我们还升级到了 10 千兆以太网交换机和网络卡。处理大型数据集时非常快速。
关于硬件定价,我们很久以前决定不对硬件成本加价。这不是我们的商业模式。相反,我们让客户准确知道硬件和保修的成本,如果需要,我们只收取我们的时间费。客户尊重我们的定价透明度,我们也收到了非常积极的反馈。客户支付托管费用,我们只有在需要时才亲自前往现场。
图片由 Gerd Altmann 提供,来自 Pixabay
另一个变化是,近年来对 MLOps(机器学习操作)的需求显著增加。在人工智能咨询领域,Kafka、Terraform 和其他工具的使用急剧上升。
MLOps 涉及使用基础设施即代码(IAC)来自动化构建、测试、部署和监控机器学习模型的过程。MLOps 工具如 Kubernetes 和 Docker 因其能够轻松管理和扩展机器学习工作负载而在人工智能咨询行业中已相当流行。但现在像 mlflow、DVC、Airflow 和 Ansible 这样的工具也逐渐成为我们工作声明中的标准组件。我们还在要求中看到许多 CI/CD 工作流和容器注册表。像 Kafka 和 Terraform 这样的组件因为速度(Kafka 很快——使用事件驱动的实时数据流——因此基本上具有高吞吐量和低延迟,只要计算资源足够)和扩展性(Terraform 使用代码而不是网页上的按钮来配置和管理资源)而获得了关注。Terraform 作为创建、管理和扩展机器学习工作负载的基础设施工具(连同 bicep 和其他工具)非常受欢迎。曾经一个 jupyter notebook 和 Keras 就足以完成项目的时代已经过去了。
合作是一个销售渠道
我们也了解到,很多机器学习(ML)团队存在,因此当合作有意义时,我们会与他们合作。这使我们能够向客户提供更广泛的服务,并帮助我们专注于机器学习而不涉及其他领域。此外,我们发现有时现成的解决方案更为合理,因此在这些情况下,我们开始接受作为工具集成商的报酬。我们发现,尤其是对于位于加拿大和美国的团队,我们能够有效合作。我特别提到这一点是因为航空航天和国防部门使得与这两个国家以外的第三方合作变得困难。目前看来,最好的合作实际上是与那些遇到他们自己无法解决的机器学习需求的前端团队。我们专注于机器学习而不涉及前端,他们则专注于前端而不涉及机器学习,因此这种合作非常契合。另一个模式是数字化转型团队,这些团队提供 IT 服务但在人工智能/机器学习方面没有深入的专业知识。我们作为他们与客户之间的增值服务进入合作关系。我们单独开具发票,但以同步的方式进行,这样合作伙伴在与最终客户的关系中与我们步调一致。
这到底意味着什么?
尽管人工智能咨询行业经历了巨大的增长,我们在定价上的方法和考虑因素保持得相当一致。我们现在提供更多的端到端服务,并继续提供透明、诚实的定价,力求在实现自身目标与满足客户需求之间找到平衡。展望今年,我预计定价将保持稳定,而且我认为企业客户将会有更多的工作转移给咨询团队。随着领域内(是的,我要提到ChatGPT)的进展速度惊人,高管们强烈意识到人工智能/机器学习必须成为他们议程的一部分,以保持竞争力。对我来说,最大的收获是我们在 2017 年时只是一个位于我车库的小创业公司,而现在我们在一个更好的办公室里,有着更大的玩具和更大的项目。我们以增加价值为定价重点,不会对硬件采购或前端开发加价,因为我们在这些方面没有增加价值。
给人工智能项目定价很复杂,但公式有效,所以我们仍然坚持使用它。
如果你喜欢这篇文章,可以看看我一些阅读量较高的旧文章,比如“大型语言模型的道德”。另外,加入我们的新闻通讯吧!
下次见!
丹尼尔·夏皮罗,博士
首席技术官,Lemay.ai
daniel@lemay.ai
如何编程一个神经网络
原文:
towardsdatascience.com/how-to-program-a-neural-network-f28e3f38e811
从头实现神经网络的逐步指南
·发表于 Towards Data Science ·阅读时间 14 分钟·2023 年 9 月 23 日
–
一个有三个隐藏层的神经网络
在本文中,我们将从头开始构建一个神经网络,并使用它来分类手写数字。
为什么要重新发明轮子/神经网络,我听到你说?难道我不能直接使用我最喜欢的机器学习框架来解决问题吗?是的,你可以使用许多现成的框架来构建神经网络(比如 Keras、PyTorch 和 TensorFlow)。使用这些框架的问题在于,它们让我们很容易将神经网络当作黑箱处理。
这并不总是坏事。我们通常需要这种程度的抽象,以便能够处理手头的问题,但如果我们要在工作中使用神经网络,仍然应该努力至少对其内部运作有一个基本了解。
从头开始构建神经网络在我看来是培养深刻理解其工作原理的最佳方式。
到本文结束时,你将了解前馈和反向传播算法,激活函数是什么,epoch 和 batch 之间的区别是什么,以及如何训练神经网络。我们将通过训练一个神经网络来识别手写数字来完成本例。
本文中使用的所有代码可以在 GitHub [1] 上找到。
什么是神经网络?
神经网络,或人工神经网络,是一种机器学习算法。它们是许多深度学习和人工智能系统的核心,例如计算机视觉、预测和语音识别。
人工神经网络的结构有时与大脑中的生物神经网络的结构进行比较。我总是建议对这种比较保持谨慎。确实,人工神经网络看起来有点像生物神经网络,但将它们与像人脑这样复杂的东西进行比较是一个很大的飞跃。
神经网络由几层神经元组成。每一层神经元的激活基于前一层的激活、连接前一层与当前层的权重集合,以及施加在当前层神经元上的偏置集合。
包含两个隐藏层的神经网络的一般结构。神经元根据其激活程度着色(激活量越大,神经元越暗)。正权重用红色表示,负权重用蓝色表示。线宽表示权重大小。
第一层是输入层。输入层的激活来自神经网络的输入。最后一层是输出层。输出层的激活是神经网络的输出。中间的层称为隐藏层。
神经网络是对函数的广义近似。像其他任何函数一样,当我们给它一个输入时,它会返回一个输出。
神经网络的新颖之处在于如何从输入到输出。这个过程由网络权重和偏置如何影响神经元激活以及这些激活如何在网络中传播,最终到达输出层来驱动。神经网络使用前馈算法将输入转换为输出。
为了使神经网络提供有用的输出,我们必须首先对其进行训练。当我们训练神经网络时,我们所做的就是通过反向传播和梯度下降迭代调整权重和偏置,以提高输出的准确性。我们计算需要将权重和偏置向哪个方向和调整多少。
前馈算法
前馈算法将我们的神经网络输入转化为有意义的输出。顾名思义,该算法将信息“向前传递”从一层到下一层。
为了理解它是如何实现的,让我们先放大来看信息是如何从一层传递到下一层的一个神经元的。
连接层 0 中神经元与层 1 中第一个神经元的权重
第二层中第一个神经元的激活a₀⁽¹⁾是通过对前一层的激活进行加权求和,再加上偏置,并通过激活函数σ(x)来计算的:
计算a₀⁽¹⁾的方程
带圆括号的上标表示层索引,从 0 开始,表示输入层。激活(a)和偏置(b)下标表示神经元索引。权重(w)下标中的前两个数字表示权重连接的神经元的索引(当前层中的)和从(前一层中的)索引。
激活函数决定一个神经元是否应根据接收到的输入而被激活。常见的激活函数包括 sigmoid、tanh、修正线性单元(ReLU)和 softmax。为了简单起见,在我们的实现中,我们将始终使用 sigmoid 激活函数。
Sigmoid、tanh 和 ReLU 激活函数
我们用来计算 a₀⁽¹⁾ 的方程可以向量化,以便我们可以计算第二层中的所有激活值:
计算 a⁽¹⁾ 的向量化方程
现在我们有了第二层的神经元激活值 a⁽¹⁾,我们可以使用相同的计算来找到 a⁽²⁾,然后是 a⁽³⁾,以此类推……
让我们看看如何在 Python 中实现这一点:
import numpy as np
import math
class Network:
def __init__(self, layers):
self.layers = layers
self.activations = self.__init_activations_zero()
self.weights = self.__init_weights_random()
self.biases = self.__init_biases_zero()
def __init_activations_zero(self):
activations = []
for layer in self.layers:
activations.append(np.zeros(layer))
return activations
def __init_weights_random(self):
weights = []
for i in range(0, len(self.layers) - 1):
weights.append(np.random.uniform(-1, 1, (self.layers[i+1], self.layers[i])))
return weights
def __init_biases_zero(self):
biases = []
for i in range(1, len(self.layers)):
biases.append(np.zeros(self.layers[i]))
return biases
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def feedforward(self, input_layer):
self.activations[0] = input_layer
for i in range(0, len(self.layers) - 1):
self.activations[i+1] = self.sigmoid(np.dot(self.weights[i], self.activations[i]) + self.biases[i])
Network
类包含有关我们神经网络的所有信息。我们通过传递一个整数列表来初始化它,该列表与每层的神经元数量相关。例如,network = Network([10, 3, 3, 2])
将创建一个输入层有十个神经元、两个隐藏层每个包含三个神经元,以及一个输出层有两个神经元的网络。
__init_*
方法初始化激活值、权重和偏置。激活值和偏置最初都是零。权重被赋予一个在 -1 和 1 之间的随机值。
feedforward
方法循环遍历各层,计算每个后续层的激活值。
下面是一个示例,使用feedforward
计算我们 [10, 3, 3, 2]
网络在给定随机输入时的输出。通过打印最终层的激活值来检查输出:
network = Network([10, 3, 3, 2])
input_layer = np.random.uniform(-1, 1, (10))
network.feedforward(input_layer)
print(network.activations[-1])
...
[0.29059666 0.5261155 ]
就这样!我们已经成功实现了前向传播算法!让我们将注意力转向反向传播。
反向传播算法
反向传播算法是神经网络从错误中学习的过程。
在上述前向传播算法的实现中,我们将网络权重初始化为 -1 和 1 之间的随机数,并将所有偏置设置为 0。使用这种初始设置,网络对任何给定输入生成的输出本质上是随机的。
我们需要一种方法来更新权重和偏置,使网络的输出变得更有意义。为此,我们使用梯度下降:
梯度下降更新步骤
其中 aₙ 是一个输入参数的向量。下标 n 表示迭代次数。f(aₙ) 是一个多变量代价函数,∇f(a) 是该代价函数的梯度。𝛾 是学习率,它决定了每次迭代中 aₙ 应调整的幅度。我之前写过一篇关于梯度下降的 文章 [2],其中详细讨论了梯度下降。
使用机器学习解决工程优化问题
towardsdatascience.com
在神经网络的情况下,aₙ 包含了所有网络的权重和偏置,f(aₙ) 是网络代价(注意 f(aₙ) ≡ C)。这里我们使用 L2-范数代价函数来定义网络代价,该函数是根据期望网络输出 ŷ 和实际网络输出 y 计算的。ŷ 和 y 都是包含 n 个值的向量,其中 n 是输出层中的神经元数量。
L2-范数代价函数
现在我们知道如何计算代价函数,但还不知道如何计算代价函数的梯度。
计算代价函数的梯度是反向传播的核心。代价函数的梯度告诉我们需要将网络中的权重和偏置向哪个方向以及调整多少,以提高输出的准确性。
代价函数的梯度
为了找到这些偏导数,神经网络采用了 链式法则。这段高中微积分的内容是神经网络工作的关键。
为了演示链式法则在反向传播算法中的应用,我们将考虑一个每层包含一个神经元的网络:
每层包含一个神经元的神经网络
我引入了一个简化版本的符号表示,因为在这个例子中我们不需要为每层中的神经元编号。下面,我还引入了一个新变量 z,它封装了激活函数的输入。
我们从输出层 L 开始反向传播,并迭代地向后通过各层。
对于输出层,我们有:
单层神经元网络输出层的 C、a⁽ᴸ⁾ 和 z⁽ᴸ⁾
现在我们已经为输出层定义了 C、a⁽ᴸ⁾ 和 z⁽ᴸ⁾,我们可以计算它们的导数并应用链式法则来找到 ∂C/∂w⁽ᴸ⁾:
应用链式法则来计算 ∂*C/*∂w⁽ᴸ⁾
对于 ∂C/∂b⁽ᴸ⁾ 也是类似的:
应用链式法则来计算 ∂C/∂b⁽ᴸ⁾
和 ∂*C/*∂a⁽ᴸ⁻¹⁾:
应用链式法则计算 ∂*C/*∂a⁽ᴸ⁻¹⁾
现在我们有了 ∂*C/*∂a⁽ᴸ⁻¹⁾ 的表达式,我们可以反向迭代通过网络找到成本函数对之前的权重和偏置的敏感性:
成本函数对 w⁽ᴸ⁻¹⁾ 和 b⁽ᴸ⁻¹⁾ 的敏感性
我们为计算这种简化的每层一个神经元网络中对权重和偏置的敏感性定义的方程,在每层有多个神经元时基本保持不变。
变化的是关于 L-1ᵗʰ 层激活值的成本函数的导数。这是因为成本函数通过网络的多个路径受到这些激活值的影响。
我们将成本函数对 L-1ᵗʰ 层激活值的导数定义为:
成本函数关于 L-1ᵗʰ 层中第 kᵗʰ 激活的导数
其中下标 j 和 k 分别表示在 Lᵗʰ 和 L-1ᵗʰ 层的激活值。
反向传播算法在 Network
类中的 backpropagation
方法中实现:
def backpropagation(self, expected_output):
# Calculate dcost_dactivations for the output layer
dcost_dactivations = 2 * (self.activations[-1] - expected_output)
# Loop backward through the layers to calculate dcost_dweights and dcost_dbiases
for i in range(-1, -len(self.layers), -1):
dactivations_dz = self.dsigmoid(np.dot(self.weights[i], self.activations[i-1]) + self.biases[i]) # Sigmoid output layer
dz_dweights = self.activations[i-1]
dz_dbiases = 1
self.dcost_dweights[i] += dz_dweights[np.newaxis,:] * (dactivations_dz * dcost_dactivations)[:,np.newaxis]
self.dcost_dbiases[i] += dz_dbiases * dactivations_dz * dcost_dactivations
# Calculate dcost_dactivations for hidden layer
dz_dactivations = self.weights[i]
dcost_dactivations = np.sum(dz_dactivations * (dactivations_dz * dcost_dactivations)[:,np.newaxis], axis=0)
注意,这个方法进行了矢量化处理,以适应每层多个神经元。dcost_dweights 和 dcost_dbiases 存储在与之前定义的权重和偏置数组相同形状的数组中。这使得使用这些偏导数进行梯度下降变得非常简单。我还认为这使得代码更具可读性。
当我们向后遍历网络时,我们对每一层应用链式法则,并使用本节中介绍的方程计算成本函数对每层权重和偏置的敏感性。
训练一个神经网络来分类手写数字
实现了前向传播和反向传播算法之后,是时候将所有内容整合起来,训练一个用于识别手写数字的神经网络了。
为此,我们需要一个标注了相应值的手写数字数据集。自己生成这个数据集将会非常费力。幸运的是,已经存在可以用于这个数字识别问题的数据库。我们将使用修改版国家标准与技术研究所(MNIST)数据库* [3],这是一个大型的标注手写数字数据库,用于训练我们的神经网络。
从 MNIST 数据库中随机选择的样本
MNIST 数据库包含 70000 个标注的灰度图像,大小为 28 x 28 像素(总共 784)。数据库中的每个标注图像称为 样本。MNIST 数据库被划分为 训练 和 测试 子集,其中包含 60000 和 10000 个样本,分别用于训练和测试。
正如它们的名字所示,训练子集用于训练网络,测试子集用于测试网络的准确性。这样我们可以使用网络从未见过的样本来测试其准确性。
接下来,我们将训练子集分割成批次。在这个例子中,我决定每个批次包含 100 个样本。总共有 600 个批次。我们将训练子集分成批次的原因是我们不会在每个样本后更新网络的权重和偏置。相反,我们是在每个批次后更新的。这样,当我们应用梯度下降时,我们使用的是基于一个批次所有样本计算的平均梯度,而不是基于单个样本的梯度来调整权重和偏置。
一个周期包含训练子集中的所有批次。一个周期会遍历所有这些批次。选择用一个周期训练我们的网络意味着网络只会“看到”训练子集中的每个样本一次。增加周期数意味着网络将对每个样本进行多次训练,从而“看到”每个样本多次。
训练工作流程图
下方显示了Network
类的完整定义,包括在训练工作流程中使用的所有方法。train_network
方法负责协调训练工作流程。
import numpy as np
class Network:
def __init__(self, layers, learning_rate):
self.layers = layers
self.learning_rate = learning_rate
self.activations = self.__init_activations_zero()
self.weights = self.__init_weights_random()
self.biases = self.__init_biases_zero()
self.G_weights = self.__init_G_weights()
self.G_biases = self.__init_G_biases()
self.dcost_dweights = self.__init_weights_zero()
self.dcost_dbiases = self.__init_biases_zero()
self.cost = 0
self.costs = []
def __init_activations_zero(self):
activations = []
for layer in self.layers:
activations.append(np.zeros(layer))
return activations
def __init_weights_random(self):
weights = []
for i in range(0, len(self.layers) - 1):
weights.append(np.random.uniform(-1, 1, (self.layers[i+1], self.layers[i])))
return weights
def __init_weights_zero(self):
weights = []
for i in range(0, len(self.layers) - 1):
weights.append(np.zeros((self.layers[i+1], self.layers[i])))
return weights
def __init_biases_zero(self):
biases = []
for i in range(1, len(self.layers)):
biases.append(np.zeros(self.layers[i]))
return biases
def __init_G_weights(self):
G_weights = []
for i in range(0, len(self.layers) - 1):
G_weights.append(np.zeros([len(self.weights[i]), len(self.weights[i][0]), len(self.weights[i][0])]))
return G_weights
def __init_G_biases(self):
G_biases = []
for i in range(0, len(self.layers) - 1):
G_biases.append(np.zeros([len(self.biases[i]), len(self.biases[i])]))
return G_biases
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def dsigmoid(self, x):
sig = self.sigmoid(x)
return sig * (1 - sig)
def calculate_cost(self, expected_output):
cost = np.sum((self.activations[-1] - expected_output)**2) # L2 cost function
return cost
def feedforward(self, input_layer):
self.activations[0] = input_layer
for i in range(0, len(self.layers) - 1):
self.activations[i+1] = self.sigmoid(np.dot(self.weights[i], self.activations[i]) + self.biases[i])
def backpropagation(self, expected_output):
# Calculate dcost_dactivations for the output layer
dcost_dactivations = 2 * (self.activations[-1] - expected_output)
# Loop backward through the layers to calculate dcost_dweights and dcost_dbiases
for i in range(-1, -len(self.layers), -1):
dactivations_dz = self.dsigmoid(np.dot(self.weights[i], self.activations[i-1]) + self.biases[i]) # Sigmoid output layer
dz_dweights = self.activations[i-1]
dz_dbiases = 1
self.dcost_dweights[i] += dz_dweights[np.newaxis,:] * (dactivations_dz * dcost_dactivations)[:,np.newaxis]
self.dcost_dbiases[i] += dz_dbiases * dactivations_dz * dcost_dactivations
# Calculate dcost_dactivations for hidden layer
dz_dactivations = self.weights[i]
dcost_dactivations = np.sum(dz_dactivations * (dactivations_dz * dcost_dactivations)[:,np.newaxis], axis=0)
def average_gradients(self, n):
# Calculate the average gradients for a batch containing n samples
for i in range(0, len(self.layers) - 1):
self.dcost_dweights[i] = self.dcost_dweights[i] / n
self.dcost_dbiases[i] = self.dcost_dbiases[i] / n
def reset_gradients(self):
# Reset gradients before starting a new batch
self.dcost_dweights = self.__init_weights_zero()
self.dcost_dbiases = self.__init_biases_zero()
def reset_cost(self):
self.cost = 0
def update_G(self):
for i in range(0, len(self.layers) - 1):
self.G_biases[i] += np.outer(self.dcost_dbiases[i], self.dcost_dbiases[i].T)
for j in range(0, len(self.weights[i])):
self.G_weights[i][j] += np.outer(self.dcost_dweights[i][j], self.dcost_dweights[i][j].T)
def update_weights_and_biases(self):
# Perform gradient descent step to update weights and biases
# Vanilla Gradient Descent
# for i in range(0, len(self.layers) - 1):
# self.weights[i] -= (self.learning_rate * self.dcost_dweights[i])
# self.biases[i] -= (self.learning_rate * self.dcost_dbiases[i])
# AdaGrad Gradient Desecent
self.update_G()
for i in range(0, len(self.layers) - 1):
self.biases[i] -= (self.learning_rate * (np.diag(self.G_biases[i]) + 0.00000001)**(-0.5)) * self.dcost_dbiases[i]
for j in range(0, len(self.weights[i])):
self.weights[i][j] -= (self.learning_rate * (np.diag(self.G_weights[i][j]) + 0.00000001)**(-0.5)) * self.dcost_dweights[i][j]
def process_batch(self, batch):
for sample in batch:
self.feedforward(sample['input_layer'])
self.backpropagation(sample['expected_output'])
self.cost += self.calculate_cost(sample['expected_output'])
def train_network(self, n_epochs, batches):
for epoch in range(0, n_epochs):
print(f"Epoch: {epoch}\n")
for batch in batches:
self.process_batch(batch)
self.costs.append(self.cost / len(batch))
self.reset_cost()
self.average_gradients(len(batch))
self.update_weights_and_biases()
self.reset_gradients()
print(f"Cost: {self.costs[-1]}")
注意,我们使用 AdaGrad 梯度下降算法来更新网络的权重和偏置。Adagrad 比传统梯度下降算法复杂一些,但在这个应用中表现更好。
接下来,我们需要定义网络的形状。每个样本中的 784 个像素值构成输入层的激活,因此输入层的大小设置为 784。由于我们正在对 0 到 9 的数字进行分类,我们也知道输出层必须包含 10 个神经元。对于隐藏层,我发现两个每层 32 个神经元的隐藏层对这个问题效果很好。
总的来说,这个网络中有 26432 个权重和 74 个偏置。这意味着在训练网络时,我们是在一个 26506 维的参数空间中进行优化!
不要被这项优化任务的规模吓倒。我们已经通过实现前馈和反向传播算法以及定义训练工作流程完成了艰巨的工作。
在训练网络之前,需要对训练数据进行一些准备工作,以将其分割成批次。然后,我们可以调用train_network
来训练网络。最后,一旦网络训练完成,我们通过检查网络对测试子集的输出,来计算网络准确率,以查看网络正确分类了多少样本。
import numpy as np
import matplotlib.pyplot as plt
from keras.datasets import mnist
from network import Network
def calculate_accuracy(network, x, y):
# Calculate network accuracy
correct = 0
for i in range(0, len(x)):
network.feedforward(x[i].flatten() / 255.0)
if np.where(network.activations[-1] == max(network.activations[-1]))[0][0] == y[i]:
correct += 1
print(f"Accuracy: {correct / len(x)}")
# Prepare training data
(train_X, train_y), (test_X, test_y) = mnist.load_data()
# Define n_epochs and set up batches
n_epochs = 5
n_batches = 600
batches = []
input_layer = np.array_split(train_X, n_batches)
expected_output = np.array_split(np.eye(10)[train_y], n_batches)
for i in range(0, n_batches):
batch = []
for j in range(0, len(input_layer[i])):
batch.append({'input_layer' : input_layer[i][j].flatten() / 255.0, 'expected_output' : expected_output[i][j]})
batches.append(batch)
# Setup and train network
network = Network([784,32,32,10], 0.1)
network.train_network(n_epochs, batches)
# Calculate accuracy of the network
calculate_accuracy(network, test_X, test_y)
...
Accuracy: 0.942
训练完成后,网络的准确率为 94.2%。对于一个从零开始构建的神经网络来说,这并不算差!
总结
在这篇文章中,我展示了如何使用 Python 从零开始构建一个简单的神经网络。
我们详细讲解了前馈和反向传播算法的实现,介绍了训练工作流程,并用 26432 个权重和 74 个偏置训练了一个神经网络来识别 MNIST 数据库中的手写数字,达到了 94.2%的网络准确率。
通过改进我们的实现可以获得更好的准确性。例如,使用 ReLU 激活函数用于隐藏层,softmax 用于输出层,已被证明能提高网络的准确性 [4]。
类似地,我们可以选择不同形式的梯度下降来调整权重和偏差,这可能使我们在 26506 维参数空间中找到更优的最小值。
将每个样本展平成一维数组的过程也会丢弃大量重要信息。更先进的卷积神经网络保留了图像中邻近像素的信息,通常比这里实现的基本网络类型表现更好。
当我开始写这篇文章时,我的目标是制作一个简明的资源,让刚接触神经网络的人能够阅读并获得对其工作原理的基本理解。我希望我达到了这个目标,并在某种程度上鼓励你继续学习这个极具趣味的主题。
这篇文章是否帮助你更深入地理解了神经网络的工作原理?请在评论中告诉我。
喜欢这篇文章吗?
关注 和 订阅 获取更多类似内容 — 与你的网络分享 — 尝试开发你自己的神经网络或尝试更先进的卷积神经网络。
除非另有说明,所有图片均由作者提供。
Yann LeCun 和 Corinna Cortes 拥有 MNIST 数据库的版权。MNIST 数据库根据 Creative Commons Attribution-Share Alike 3.0 license的条款提供。
参考文献
[1] GitHub (2023), artificial_neural_network
[2] Bruce, C. (2023). PID Controller Optimization: A Gradient Descent Approach. Medium
[3] Deng, L. (2012). The MNIST database of handwritten digit images for machine learning research. IEEE Signal Processing Magazine, 29(6), 141–142
[4] Nwankpa, C., Ijomah, W.L., Gachagan, A., & Marshall, S. (2018). Activation Functions: Comparison of trends in Practice and Research for Deep Learning. ArXiv, abs/1811.03378
如何在 Amazon ECS 上将 ML 模型正确部署为 Flask APIs
原文:
towardsdatascience.com/how-to-properly-deploy-ml-models-as-flask-apis-on-amazon-ecs-98428f9a0ecf
在 Amazon ECS 上部署 XGBoost 模型以推荐完美的小狗
·发布在 Towards Data Science ·阅读时间 8 分钟·2023 年 3 月 16 日
–
图片由 Carissa Weiser 提供,来自 Unsplash
随着 ChatGPT 的大获成功,AI 技术对我们生活的影响变得越来越明显。然而,除非这些出色的 ML 模型向所有人开放并且正确部署以应对高用户需求,否则它们将无法对世界产生任何积极影响。因此,能够不仅开发 AI 解决方案,还要了解如何正确部署它们是如此重要。更不用说,这项技能将使你在市场上变得更有价值,并开启有利可图的 ML 工程师职业机会。
在这篇文章中,我们将把 XGBoost 模型部署为 Flask API,并使用 Gunicorn 应用服务器在 Amazon Elastic Container Service 上进行部署。该模型将根据某人家的大小推荐一只腊肠犬或德国牧羊犬小狗。
图片由作者提供,来源: [1–2]
👉 游戏计划
-
训练一个 XGBoost 模型
-
构建一个简单的 Gunicorn-Flask API 进行推荐
-
为 API 构建 Docker 镜像
-
在 Amazon ECS 上部署 Docker 容器
完整源码 GitHub 仓库: 链接🧑💻
flask-on-ecs - repo structure
.
├── Dockerfile
├── README.md
├── myapp.py
├── requirements.txt
└── train_xgb.ipynb
在云上部署 ML 模型
我们通常需要将本地训练的 ML 模型部署到生产环境中供互联网用户使用。这种方法需要首先将 ML 模型封装成 API,然后进行 Docker 化。AWS 提供了一个名为 Elastic Container Service(ECS)的专用工具,它消除了管理计算环境(如 EC2)的麻烦,使我们可以使用名为 Fargate 的无服务器工具简单地部署 Docker 容器。
关于 Web 服务器与应用服务器的说明
在传统的 web 开发世界中,通常的做法是让 Web 服务器(如 NGINX)处理来自客户端的大量流量,并与提供动态内容的后台应用程序(API)进行交互。可以将 Web 服务器视为餐厅中的服务员,服务员接收并处理顾客的订单。类似地,Web 服务器接收并处理来自 Web 客户端(如 web 浏览器)的请求。服务员随后与厨房沟通以准备订单,并将完成的餐点送到顾客那里。同样,Web 服务器与后台应用程序沟通以处理请求,并将响应发送回 Web 客户端。设置通常如下:
作者提供的图片
Web 服务器非常棒,因为它们能够将客户端请求分发到多个后台应用程序,从而提高性能、安全性和可扩展性。然而,在我们的案例中,由于我们将在 AWS 上部署 Flask API,因此有云原生的负载均衡器可以处理流量路由到后台 API,并且还可以强制执行 SSL 加密。因此,包含 NGINX 会显得有些多余。仅使用 Gunicorn 应用服务器对于大多数 ML 模型部署情况而言是足够的,前提是你打算使用 AWS 负载均衡器。好吧,但……
什么是 WSGI 和 Gunicorn?
WSGI(Web 服务器网关接口)只是一个约定或 规则集,在 web 服务器与 web 应用程序通信时需要遵循。Gunicorn(绿色独角兽)是一个遵循 WSGI 规则并处理客户端请求的 应用服务器,将请求发送到 Python Flask 应用程序。Flask 本身提供了 WSGI Werkzeug 的开发服务器用于初步开发,但如果我们要在生产中部署 API,那么我们的 Flask 应用程序可能需要同时处理多个请求,因此我们需要 Gunicorn。
实时 API 的典型云架构
在云中运行 API 得到了应用负载均衡器(ALBs)的极大提升,因为它们通常可以代替 NGINX 的功能并将流量路由到我们的后台应用程序。这个教程将仅专注于在 ECS 上部署 Flask API,我们可以在未来的帖子中讨论 ALBs。
作者提供的图片
好了,背景介绍完了,我们开始构建和部署一些 API 吧!
👉 第一步:训练 XGBoost 模型
训练一个 XGBoost 模型,根据房屋面积预测 Dachshund(腊肠犬)或 German Shepherd(德国牧羊犬),并将模型保存为 pickle 文件。
要在 VS Code 中运行它,我们来创建一个单独的 Python 3.8 环境:
conda create --name py38demo python=3.8
conda activate py38demo
pip install ipykernel pandas flask gunicorn numpy xgboost scikit-learn
然后重新启动 VS Code 并在 Jupyter Notebook 中 -> 选择 ‘py38demo’ 作为内核。
训练并 pickle XGBoost 模型:
如您所见,我们成功训练了模型,在 300 和 600 平方英尺的房屋上进行了测试,并将 XGBoost 模型保存为 pickle (.pkl) 文件。
👉 步骤 2:构建一个简单的 Gunicorn-Flask API
让我们构建一个非常简单的 Flask API 来提供我们的 XGBoost 模型预测。我们有一个简单的助手函数,将 0/1 模型预测转换为 ‘wiener dog’/‘german shepherd’ 输出:
要运行 API,请在终端中:
python myapp.py
在另一个终端中,测试一下发送 POST 请求:
curl -X POST http://0.0.0.0:80/recms -H 'Content-Type: application/json' -d '{"area":"350"}'
我是如何在我的 Mac 上本地运行它的:
我们的 API 工作得很好,但你可以看到我们收到一个警告,说明这是一个开发服务器:
让我们停止 API,改用 Gunicorn 生产级服务器:
gunicorn --workers 4 --bind 0.0.0.0:80 myapp:flask_app_obj
回到我们的 VS Code:
现在我们准备好将 API Docker 化了!📦
👉 3. 为 API 构建 Docker 镜像
以下是一个 Dockerfile,它使用 python3.8 基础镜像。我们需要 3.8 版本,因为我们在本地使用该版本训练了 XGBoost 模型。
注意:由于我在 Mac 上构建镜像,我需要指定
- -platform linux/amd64
使其与 ECS Fargate Linux 环境兼容。
这是我们构建和运行镜像的方法。
注意:我们将主机(即笔记本电脑)的 80 端口绑定到 Docker 容器的 80 端口:
docker build --platform linux/amd64 -t flaskapi .
docker run -dp 80:80 flaskapi
让我们快速再测试一下:
现在我们知道我们的 API 在 Docker 容器内运行良好,是时候将其推送到 AWS 了!🌤️
👉 步骤 4:在 Amazon ECS 上部署 Docker 容器
这个部分看起来可能很复杂,但实际上,如果我们将过程分解为 6 个简单步骤,就会很简单。
图片由作者提供
i) 将 Docker 镜像推送到 ECR
让我们创建一个名为 demo 的 ECR 仓库,在这里我们可以推送 Docker 镜像。
然后我们可以使用 ECR 提供的推送命令:
# autheticate
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <Your-aws-acc-no>.dkr.ecr.us-east-1.amazonaws.com
#tag the image
docker tag <Your-local-docker-image-name>:latest <Your-aws-acc-no>.dkr.ecr.us-east-1.amazonaws.com/<Your-ECR-repo-name>:latest
#push the image to ECR
docker push <Your-aws-acc-no>.dkr.ecr.us-east-1.amazonaws.com/<Your-ECR-repo-name>:latest
假设:你已经在本地计算机上配置了 AWS CLI,并设置了具有正确权限的 IAM 用户来与 ECR 进行交互。你可以在此 链接 中找到更多信息。
运行上述 3 个命令后,我们可以看到我们的镜像已经在 ECR 上 🎉
复制并粘贴图像 URI 到某处,因为我们在接下来的步骤中需要它。
ii) 创建一个 IAM 执行角色
我们需要创建一个执行角色,以便我们的 ECS 任务能够从 ECR 拉取镜像。我们将其命名为:simpleRole
iii) 创建一个安全组
安全组是必需的,以允许互联网中的任何人向我们的 API 发送请求。在现实中,你可能希望将其限制为特定 IP 集合,但我们将其开放给所有人,并称之为:simpleSG
iv) 创建一个 ECS 集群
这一步很简单,只需几秒钟。我们称之为:flaskCluster
在我们的集群正在配置的同时,让我们创建一个任务定义。
v) 创建任务定义
任务定义,顾名思义,是一组与要运行的镜像、要打开的端口以及我们希望分配的虚拟 CPU 和内存相关的指令。我们称之为:demoTask
vi) 运行任务
让我们在flaskCluster上运行我们的demoTask,使用来自**步骤 iii)的simpleSG。
该测试我们部署的 API 从公共 IP地址开始! 🥁
curl -X POST http://<PUBLIC-IP>:80/recms -H 'Content-Type: application/json' -d '{"area":"200"}'
它运行正常! 🥳
正如你所见,我们通过向 ECS 提供的公共 IP发送 POST 请求,成功获得了完美的狗狗推荐。 🔥
感谢阅读,希望你发现这对入门 Flask、Gunicorn、Docker 和 ECS 有所帮助!
想要更多关于机器学习工程的实用文章吗?
免费订阅 以便在我发布新故事时收到通知。
成为 Medium 会员以阅读我和其他成千上万位作者的更多故事。你可以通过使用我的 推荐链接 来支持我。你不会额外支付费用,但我会获得佣金。
参考资料
[1] 腊肠犬图片:链接
[2] 德国牧羊犬图片:链接
如何使用 Quip Python APIs 从/到相同的 Quip 电子表格读取和写入数据
我们分析师经常被要求提供一种解决方案,使最终用户能够提供其输入,这些输入随后可以用作最终分析解决方案中的覆盖/附加上下文。
·
跟进 发表在 Towards Data Science ·7 min read·Nov 17, 2023
–
Chris Ried的照片在Unsplash上发布
让我们以一个电子商务购物应用程序为例。系统中有一个逻辑,会在供应商收到 100 个负面客户评分后将其列入黑名单。现在,可能会出现负面评分是由于应用内体验或配送/退货体验引起的情况。因此,为了保持公平,供应商可以选择每六个月对黑名单提出异议。为了方便本文讨论,我们假设审批/拒绝会记录在离线电子表格中。
所有新的撤销黑名单申请在一周内会被导出到一个电子表格,并发送给团队进行审查。团队会审查数据并批准或拒绝这些申请。然后,他们会将数据返回系统进行更新。这是每周进行的工作。
现在,这些手动干预的数据需要被添加到系统中。可以有多种方法来实现。用户可以将他们的数据上传到一个 s3 桶中,然后可以安排将其读取到数据库中。或者,我们可以使用 quip,这样所有个人可以实时更新同一个电子表格,然后可以按照固定的节奏将其上传到数据库中。
Quip 是一款协作软件,允许多人实时编辑文档和电子表格,用户可以自由选择任何终端客户端——桌面/移动设备。
在这篇文章中,我将展示如何将一个 quip 电子表格自动化,以读取用户输入的数据,上传到数据库表中,然后将新的数据写回到同一个电子表格。我将使用 Redshift 作为这次练习的数据库。
这个任务可以分为两个独立的部分。首先,从 Quip 电子表格读取数据并将其存储在数据库中的表中。其次,我们将对这些数据进行一些操作或检查,并与数据库中现有的数据进行联接,然后将处理后的数据写入到已经存在的 quip 电子表格中。我们将分别讨论这两个情况,以便如果您只想读取或只想写入,这篇文章也能帮助您完成这项工作。让我们来看第一个部分。
第一部分 — 从 Quip 电子表格读取数据并将其写入数据库中的表。
步骤 1: 获取访问令牌以通过 Quip API 连接到 Quip。
我们需要生成一个访问令牌,以提供对个人 Quip 账户的 API 访问权限。要生成个人访问令牌,请访问此页面:quip.com/dev/token
。如果您有企业 SSO 启用的 Quip 账户,那么 URL 会有所不同,例如 — quip-corporate.com/dev/token
一旦您点击上方的获取个人访问令牌按钮,您将获得一个令牌,我们将在后续部分使用该令牌通过 API 访问 quip 电子表格。
步骤 2: 导入库
首先,让我们导入所需的库。在这一部分,我们主要使用 quipclient 和 pandas_redshift 库。
import quipclient as quip
import pandas as pd
import numpy as np
import pandas_redshift as pr
import boto3
from datetime import datetime as dt
import psycopg2
import warnings
warnings.filterwarnings('ignore')
import socket
步骤 3: 使用令牌 ID 连接到 Quip
QuipClient API 需要基本 URL、线程 ID 和访问令牌来访问任何文件。基本 URL 是你尝试读取(或写入)的 quip 服务器的 URL。在公司账户的情况下,这通常会在 URL 中包含公司名称。线程 ID 是 Quip 服务器上所有文件的唯一标识符。它是目标文件的基本 URL 之后的字母数字值,在这个例子中是一个电子表格。
如果文件的 URL 看起来像 — https://platform.quip-XXXXXXXXX.com/abcdefgh1234/,则基本 URL 将是 — https://platform.quip-XXXXXXXXX.com,thread_id 将是 — abcdefgh1234。
访问令牌是我们在第 1 步中刚刚生成的。
现在,使用 QuipClient API,我们通过访问令牌和 thread_id 连接到 URL。
#####################################
# declaring Quip variables
#####################################
baseurl = 'https://platform.quip-XXXXXXXXX.com'
access_token = "************************************************************************"
thread_id = 'abcdefgh1234'
##########################################
# connecting to Quip
##########################################
client = quip.QuipClient(access_token = access_token, base_url=baseurl)
rawdictionary = client.get_thread(thread_id)
第 4 步:从 quip 中读取数据到数据框
第 3 步中的 rawdictionary 输出返回一个 HTML 列表。Pandas 函数 read_html 将帮助将 HTML 部分读取到数据框 dfs 中。因此,dfs 是一个数据框的列表。这个列表中的每个数据框包含来自 quip 电子表格中每个选项卡的数据。在这个例子中,我们只考虑最后一个选项卡的数据。因此,使用索引 -1 来获取 raw_df 中的最后一个数据框。
##########################################
# cleaning the data and creating a dataframe
##########################################
dfs=pd.read_html(rawdictionary['html'])
raw_df = dfs[-1]
raw_df.columns=raw_df.iloc[0] #Make first row as column header
raw_df=raw_df.iloc[1:,1:] #After the above step, the 1st two rows become duplicate. Delete the 1st row.
raw_df=raw_df.replace('\u200b', np.nan) #Replacing empty cells with nan
第 5 步:连接到数据库以将数据写入表中
要访问 Redshift 实例,我们需要 Redshift Endpoint URL。例如,实例将如下所示:
datawarehouse.some_chars_here.region_name.redshift.amazonaws.com.
我们连接到数据库,并将数据框(在第 4 步中创建)写入新表或现有表中。pandas_to_redshift 函数允许你将数据附加到现有表中或完全覆盖它。请注意,如果选择 append = False,那么每次执行此操作时,表将被删除并重新创建。如果你希望在覆盖数据时保留某些列的数据类型或字符长度,或者用户权限,最好在执行此操作之前先截断表。你可以通过发出直接的 Truncate 命令来截断表。SQLAlchemy 和 psycopg2 是更简单的选项。截断表后,你可以使用 append = True 执行操作。我通常对需要保留历史数据的类型 2 表使用 append=True。
#### Truncating the Table ####
##########################################
# Connecting to DataBase
##########################################
user1="user"
pswd1="password"
connection_db=psycopg2.connect(dbname = 'test_db',
host='test_host.com',
port= '1234',
user= user1,
password= pswd1)
##########################################
# Connection Established
##########################################
df = pd.read_sql_query("""Select distinct
from test_table
order by 1 asc
""",connection_db)
result = df.to_markdown(index=False)
cur = connection_db.cursor()
cur.execute('TRUNCATE TABLE test_table')
#### Writing to the table ####
##########################################
# connecting to redshift and s3
##########################################
pr.connect_to_redshift(dbname = 'db',
host = 'server.com',
port = 1234,
user = 'user',
password = 'password')
pr.connect_to_s3(aws_access_key_id = '*************',
aws_secret_access_key = '*************************',
bucket = 'test',
subdirectory = 'subtest')
##########################################
# Write the DataFrame to S3 and then to redshift
##########################################
pr.pandas_to_redshift(data_frame = raw_df,
redshift_table_name = 'test_table',append = True,
region = 'xxxxxxx')
这完成了第一部分,即从 quip 电子表格中读取数据并写入 Redshift 表。现在,让我们看一下第二部分。
第二部分:将数据写入现有的 Quip 电子表格。
对于这一部分,前三个步骤与第一部分相同。所以,请遵循上述第 1、2 和 3 步。我们将从这里的第 4 步开始。
第 4 步:连接到数据库以读取数据
我们将在这里使用 psycopg2 连接到 Redshift 实例,并从 Redshift 表中读取需要写入 Quip 电子表格的数据。在这里,我将数据框转换为 Markdown 以获得一个干净的表格,这也是 QuipClient 库的一个先决条件。
##########################################
# Connecting to DataBase
##########################################
user1="user"
pswd1="password"
connection_db=psycopg2.connect(dbname = 'test_db',
host='test_host.com',
port= '1234',
user= user1,
password= pswd1)
##########################################
# Connection Established
##########################################
df = pd.read_sql_query("""Select distinct
from test_table
order by 1 asc
""",connection_db)
result = df.to_markdown(index=False)
第 5 步:将数据写入 Quip 文件
要将数据写入 Quip 电子表格,可以使用QuipClient库中的edit_document函数。此函数具有多个参数。格式可以是 HTML 或 markdown。默认为 HTML,这就是我们在第 4 步将数据框转换为 markdown 的原因。您需要指定section_id和location来指定要添加数据的位置——追加、前置、在特定部分之后/之前等。针对这种特定情况,我只想将数据追加到现有电子表格的新标签页中。您可以在这里了解更多信息。
有时,操作已执行,但脚本仍因 API 响应延迟而失败。try-except 错误块用于捕获任何超时错误。
##########################################
# Inserting the data to Quip
##########################################
try:
client.edit_document(thread_id=thread_id,
content = result,
format='markdown',
operation=client.APPEND)
print("Test DB is updated.")
except socket.timeout:
print("Error Excepted!")
我们完成了!
希望您找到本文有帮助。如果您有任何额外的问题,请随时联系。
感谢您的阅读!
下次再见……
如何减少你的主数据管理费用
原文:
towardsdatascience.com/how-to-reduce-your-master-data-management-bill-a95bf45b2353
利用开源工具抓住“低垂的果实”
·发表于Towards Data Science ·阅读时间 9 分钟·2023 年 4 月 28 日
–
主数据管理(MDM)是商业供应商用来描述实体解析框架的流行词。我与几家供应商进行了交谈,大多数供应商提供 SaaS,并按从来源中摄取的记录总数定价。这对于大型企业来说,每年的费用在 6 到 7 位数美元之间。
这篇文章的目标读者
你计划很快实施 MDM 吗?你是否向供应商询问了报价?或者你的公司是否已经投资了 MDM SaaS?这无疑不是一项小投资。
如果你能通过几天的工程工作显著减少年度订阅费用呢?用一句话描述这个想法:
利用开源工具抓住“低垂的果实”,让 MDM 完成艰巨的工作。
这种做法可以轻松转化为每年节省两位数百分比或 5 到 7 位数美元。
为什么实体解析很重要
一般规模较大的企业使用多个数据源。这些数据源用于其运营(ERP 系统)、客户关系管理(CRM)、分析(数据湖、数据仓库)等(文件系统、外部来源)。
几乎每个系统中都存在冗余记录。一些交易正式链接到记录 AB InBev,另一些则链接到 AB INBEV NV。如果重复记录未被发现,我们将失去对单一客户实体的完整视图。客户重复记录的示例取自我写的一篇文章。图片由作者提供。
相同现实世界客户实体的记录隐藏在不同的来源中;并非所有记录都通过外键链接或所有属性都同步,每个来源中都存在重复记录。这是一个重要的数据质量问题。并且不仅仅是客户实体,还包括供应商、产品、人员和其他实体类型。
我知道一家通过许多并购实现增长的公司。业务随着时间的推移整合了许多新的产品线和地理区域。但 IT 整合迅速滞后,操作了 100 多个 ERP 系统,团队继续在孤岛中工作。这导致了错失协同机会。举几个例子:
-
跨产品线和地区的交叉销售机会被错过了。
-
由于地区和产品线的遗留界限,现场工作的团队利用不够理想。
-
与供应商的谈判不够理想,因为各团队独立采购相同的产品。
-
因为制造/采购和销售之间缺乏透明度,订单积压失控。
大型企业在职能、地区和业务线之间维护许多 IT 系统。箭头表示已实施的过程、数据流或只是手动文书工作。我们能否追踪一个产品从关闭机会到制造、分销、安装/销售和服务的全过程?图片来源于作者。
但它不需要永远保持这样。我在下面概述了一个简化的架构。MDM 平台处理从头到尾的实体解析过程。结果是一组客户、产品、人员和供应商的交叉引用——所有源的联接键查找表。我们将这些与其他信息(订单、报价、交易等)的综合视图结合起来,以克服上述挑战。
我们从源头提取并加载(EL)数据到湖中的原始层。一部分数据流入我们的主数据管理(MDM)平台,MDM 平台处理从头到尾的实体解析。转换(T)补充了 MDM 工作,形成一个完整的数据集市。分析和反向 ETL 提供可操作的洞察。图片来源于作者。
实体解析的端到端工作原理
Christophides 和合著者的文章 Big Data 的端到端实体解析:调查 提供了深入的概述——这是一个关于实体解析方法论的精彩综述。不要错过我们在这里不会涉及的许多主题。
下一张图代表了实现实体解析的众多方式之一。
实体解析可以是一个迭代过程。我们摄取和预处理记录,工程化相似性特征,选择(和拟合)分类模型,并聚类匹配。我们可以设置规则(相似性阈值),在这些规则下高度相似的对会被自动视为真实匹配(红色),并将一批可能但不确定的案例分配给人工审核(绿色)。解决的例子帮助我们学习和改进。图片来源于作者。
从高层来看,这些是典型的端到端过程的步骤:
-
预处理/规范化时仅保留语义。
-
构建记录块,限制比较的数量。
-
工程化特征以衡量属性的相似性。
-
选择(并调整)一个模型来预测成对匹配的可能性。
-
将成对匹配转换为实体集群。
-
与人工审核(批量)可能但不确定的例子。
通常,你会将一批不确定的案例分发给人工审核。标注的结果可以用来重新调整你的分类模型,甚至让你重新考虑任何一个初步步骤(预处理、阻塞、特征工程)。更强大的模型可以检测到更多值得审核的有趣案例,使得这个过程具有迭代性。
为什么不在内部构建你的实体解析框架?
通常高昂的计算成本加上人工参与的审核过程增加了问题的另一个维度:预算。你不希望你的云账单或劳动成本飙升。
因此,一个快速且便宜的解决方案在操作上可能过于昂贵。你决定选择更复杂的版本。还有更多你希望包含的组件,超过了我们在前面部分讨论的内容。例如,使用弱监督根据领域专家提供的启发式规则对成对数据进行标注。或者主动机器学习根据估计的不确定性优先考虑样本以进行人工审核。
每个组件单独来看都像是一个可管理的任务。真正的挑战在于构建和管理所有内容所需的多样技能:处理基础设施和安全,构建后台、分类模型,以及为审核员构建前端。
你也可以构建一些你团队更有信心的组件,然后让供应商处理其余的繁重工作。我与两个提供强大匹配引擎作为产品的供应商进行了交流——你必须在自主管理的基础设施上安装的软件。我还与提供 SaaS 注释服务的供应商进行了交流,用于管理审核任务。
听起来像是很多讨论。但这也是一个快速学习的机会。我还建议先用开源框架进行实验,再与供应商交谈。个人经验中的一些好处:
-
避免营销噪音电话,因为你已经知道你想要什么。
-
挑战供应商解决你在使用开源软件时发现的边缘案例。让他们找到解决方案。
-
确定每个供应商的弱点——他们都有!
-
更加自信地进行谈判。告诉他们你了解他们的弱点,并且你不是一个容易对付的对象。毫无疑问,这将大大影响他们产品的定价。
如何显著降低你的 MDM 成本
我与大多数 MDM 供应商交流过,他们的定价基于导入到平台上的总记录数。但这还不是全部。他们还会试图向你推销与外部 API 的集成,例如地址验证。
下图详细展示了数据准备步骤。我用绿色标出了节省资金的机会。
每个绿色框都是一个节省资金的机会。预处理(例如 SQL)帮助我们筛选出相关记录。开源实体解析处理简单案例,再次减少输入到 MDM 的记录数量。最后,只有在无法用便宜的替代品替换时,才调用昂贵的第三方 API。图片由作者提供。
你必须投资以抓住每一个节省资金的机会。让我们从投资较低的机会开始:
通过简单的预处理来节省资金
你的源系统中的客户记录并非全部同等重要。许多记录可能对业务没有任何价值或不符合你的 MDM 业务案例。
我们将相关数据提取+加载到数据湖中。有些记录对 MDM 没有益处。我们可以通过规则(转换为 SQL 并在数据湖中执行)来识别这些记录。这些规则可以随时更改。图片由作者提供。
-
僵尸记录没有关联到单一订单、交易、合同、开放机会或其他与操作相关的实体。因此,你可能不会从解决这些无效记录中受益。
-
解决你的 B2C 客户的可能性有多大?MDM 的卖点是提供跨地区、产品线等的客户 360 度视图。如果在你的业务中,这对 B2C 很少有用,那么为何要投资解决这些实体?
一般思路是收集具有显著价值的业务案例。然后,通过诸如“我们是否需要处理没有任何收入的客户记录来满足你的需求?”之类的问题来挑战业务。所有的答案汇总将识别值得纳入 MDM 的子集。
排除记录不是一个永久的决定。新的业务案例是否足以证明重新纳入先前排除的记录?提交代码更改;数据将被包含在下一个 MDM 批次中。
通过更便宜的第三方 API 替代品来节省资金
如果不将 MDM 与第三方 API 集成,你将无法释放 MDM 的全部潜力。两个显著的例子是:
-
地理编码和验证地址。
-
为 B2B 客户提供行业分类、层级(母公司、子公司)及其他关键绩效指标(年收入、员工人数)。
典型的 MDM 供应商会尝试向你销售内部解决方案或市场领导者以确保安全——没人因为购买 IBM 而被解雇。但这真的是你为业务获得的最佳价值吗?
使用友好的服务对地址进行地理编码。一些服务不仅提供搜索结果,还会给出置信度评分。如有必要,对于低于阈值的评分,可以调用第二种更昂贵的服务。图片由作者提供。
以地理编码服务为例。Google Maps和Mapbox是两个突出的市场领先者,还有很多其他供应商提供闭源专有解决方案。另一方面,像 Geoapify 和 Opencage 这样的供应商依赖开源和开放数据,特别是 OpenStreetMap 生态系统。这些开放的替代方案提供了远低于闭源竞争对手的价格。更重要的是,它们带有友好的许可,允许你在没有限制的情况下存储和分享数据。
你说 Google Maps 在你的数据上比 OpenStreetMap 更准确?没问题。如果首选服务的信心较低,你可以使用其他服务作为后备。
使用开源实体解析节省开支
许多 MDM 供应商提供的功能在流行的开源替代品中很难找到——专有的发音算法、集体匹配、以实体为中心的匹配等。这些将帮助你捕捉到你可能会错过的边缘案例。
使用开源工具来衡量实体记录对的相似度。让复杂的专有 MDM 解决方案为你做繁重的工作。提高你的投资价值。图片由作者提供。
那大部分情况呢?根据我的经验,大多数检测到的重复项都是低悬的果实——如果你问我,大约 80 比 20 的比例。我们可以通过简单的开源实体解析步骤快速抓取 80%。选择一个相对保守的匹配相似度阈值并自动解析。假设你的数据包含 20%的冗余记录(根据个人经验估算),我们可以在将数据导入 MDM 之前减少 16%的样本量。
从架构上讲,我们可以将这样的步骤作为脚本部署在我们的数据湖上,并在提取和加载源数据后执行。我们可以将协调开销保持在最低限度。可能一次性作业会清理大部分数据,而每隔一段时间执行一次就能完成其余部分。
我们可以将输出的检测到的低悬果实对存储在交叉引用表中,并将其与 MDM 的结果结合使用,以获得完整的视图。
证明概念并自信谈判
MDM 是一项昂贵的长期投资。一个合理的方式来证明其价值是通过几天/几周的工作——在公司内部数据上的概念验证(POC)。
我们最关键的数据源中有多少冗余的客户/产品/供应商记录?跨源的参考差距有多大?这些如何转化为低效?利益相关者需要在投资昂贵解决方案之前有一些粗略的估计。
你可以在几天内对数据的关键子集进行 POC 测试。检查一下开源实体解析框架。但不仅仅报告你能自信检测到的重复项数量。通过随机抽样和手动努力调查可能但不确定的情况。
你使用开源工具和几行代码自信地检测到了许多重复项(绿色)。你的算法在哪些方面需要赶上?通过随机分层抽样和一些老式的手动调查工作来了解。图片由作者提供。
你的内部解决方案在哪些方面需要赶上?拼写错误是否合理?对同义词或缩写不了解?在非拉丁语言中表现差劲?挑战 MDM 供应商,看看他们是否能更有信心地解决这些问题。如果你最喜欢的供应商在任何方面落后,用你的证据谈判价格——这是降低 MDM 费用的另一种方式。
结论
MDM 平台在绝对金额上是昂贵的。供应商通过这些平台产生的价值来证明其价格标签。我同意这一点。然而,我看到显著增加投资回报的潜力。
那为什么不完全在内部构建呢?你可以保持复杂度低和架构简单。例如,一个简单的脚本加上保守的阈值将比什么都没有要好。真正的问题是,除了这些,你是否能从实体解析中获得更多收益?一些考虑因素:
-
将实体解析视为业务问题,而非 IT 问题。收集具有显著估计价值的业务案例。展示内部解决方案与购买解决方案相比,你能做什么和何时能做。
-
你的团队是否具备构建内部解决方案的专业知识?你不希望仅仅为了实体解析而雇佣一整支工程师团队。
-
最后,MDM 价格存在显著差异。如果预算是一个问题,避免市场领导者。许多供应商在这个领域竞争。有些供应商的价格范围非常低,远低于一整支内部工程师团队的薪资。
如何在 5 个简单示例中将 SQL 查询重写和优化为 Pandas
编程
从 SQL 过渡到 Pandas 以改善你的数据分析工作流程
·发布于 Towards Data Science ·9 分钟阅读·2023 年 6 月 1 日
–
作者提供的图片
数据分析师、工程师和科学家都熟悉 SQL。该查询语言仍广泛用于处理任何类型的关系数据库。
然而,现在越来越多的情况,尤其是对于数据分析师,技术要求在上升,人们预计至少要了解一种编程语言的基础知识。在处理数据时,Python 和 Pandas 特别是常见的工作要求之一。
尽管 Pandas 对于熟悉 SQL 的数据人员来说可能是新的,但在 SQL 中选择、过滤和聚合数据的概念可以很容易地转移到 Pandas 中。在这篇文章中,让我们看看一些常见的 SQL 查询以及如何在 Pandas 中编写和优化它们。
随意在你自己的笔记本或 IDE 中跟随操作。你可以从 Kaggle 这里 下载数据集,该数据集在 CC0 1.0 Universal (CC0 1.0) 公共领域献身许可证下免费使用。
只需导入并运行以下代码,开始吧!
import pandas as pd
from functools import reduce
df = pd.read_csv("/Users/byrondolon/Desktop/Updated_sales.csv")
df.columns = [i.replace(" ", "_") for i in df.columns]py
SQL 的简单示例及其 Pandas 等效方法
查询整个表
我们可以直接开始,看看经典的 SELECT ALL from 一个表。
这是 SQL:
SELECT * FROM df
这是 Pandas 代码
df
Pandas 代码输出 — 作者提供的图片
你需要做的就是调用 Pandas 中的 DataFrame 来返回整个表及其所有列。
你可能还希望在编写更复杂的查询之前快速检查表中的一个小子集。在 SQL 中,你可以使用 LIMIT 10
或类似的方式仅获取选定数量的行。在 Pandas 中,你可以调用 df.head(10)
或 df.tail(10)
来获取表的前 10 行或最后 10 行。
查询没有空值的表
要在最初的选择查询中添加条件,除了限制行数之外,你还可以在 SQL 的 WHERE 子句中放入过滤表的条件。例如,如果你想要所有 Order_ID
列中没有任何空值的行,SQL 查询将如下所示:
SELECT * FROM df WHERE Order_ID IS NOT NULL
在 Pandas 中,你有两个选项:
# Option 1
df.dropna(subset="Order_ID")
# Option 2
df.loc[df["Order_ID"].notna()]
Pandas 代码输出 — 作者提供的图片
现在,我们得到的表没有 Order_ID
列中的任何空值(你可以与上面的第一个输出进行比较)。这两种方法都会返回没有空值的表,但它们的工作方式略有不同。
你可以使用 Pandas 中的 dropna
方法返回没有任何空行的 DataFrame,指定 subset
参数中要从中删除空值的列。
另外,loc
方法允许你传递一个掩码或布尔标签来过滤 DataFrame。在这里,我们传递 df["Order_ID"].notna()
,如果你单独调用它,将返回一个 True 和 False 值的系列,这些值可以映射到原始 DataFrame 行,以确定 Order_ID
是否为空。当我们将它传递给 loc
方法时,它会返回 df["Order_ID"].notna()
评估为 True 的 DataFrame(即所有 Order_ID
列不是空的行)。
查询表中的特定列
接下来,我们将选择表中的几个特定列,而不是选择所有列。在 SQL 中,你可以在查询的 SELECT 部分中写入列名,如下所示:
SELECT Order_ID, Product, Quantity_Ordered FROM df
在 Pandas 中,我们将代码写成这样:
df[["Order_ID", "Product", "Quantity_Ordered"]]
Pandas 代码输出 — 作者提供的图片
要选择特定的列子集,你可以将列名列表传递给 Pandas 中的 DataFrame。你也可以像这样单独定义列表以便清晰:
target_cols = ["Order_ID", "Product", "Quantity_Ordered"]
df[target_cols]
分配一个目标列列表,然后将其传递到 DataFrame 中,可以使在需要对代码进行更改时处理表变得更加容易。例如,你可以让一个函数返回你需要的列作为列表,或者根据用户需要的输出来追加和删除列表中的列。
SQL 和 Pandas 中的 GROUP BY
现在我们可以开始聚合数据。在 SQL 中,我们通过将要分组的列传递给 SELECT 和 GROUP BY 子句来实现这一点,然后在 SELECT 子句中将该列添加到像 COUNT 这样的聚合度量中。例如,这样做将允许我们对原始表中每个 Product
的所有单独的 Order_ID
行进行分组,并计算它们的数量。查询可能如下所示:
SELECT
Product,
COUNT(Order_ID)
FROM df
WHERE Order_ID IS NOT NULL
GROUP BY Product
在 Pandas 中,它会像这样显示:
df[df["Order_ID"].notna()].groupby(["Product"])["Order_ID"].count()
Pandas 代码输出 — 图片由作者提供
输出是一个 Pandas Series,其中表格按产品进行分组,并显示每个产品的Order_ID
计数。除了我们之前在 Pandas 中包含过滤条件的查询外,我们现在要做三件事:
-
添加
groupby
并传递你想要对 DataFrame 进行分组的列(或列列表); -
在原始分组的 DataFrame 上用方括号传递列名;
-
调用
count
(或任何其他聚合)方法对 DataFrame 中的目标列进行聚合。
为了更好地阅读,我们可以将条件赋值给一个变量(这在后面会派上用场),并格式化查询,使其更易读。
condition = df["Order_ID"].notna()
grouped_df = (
df.loc[condition]
.groupby("Product")
["Order_ID"] # select column to count
.count()
)
grouped_df
一个完整的 SQL 查询在 Pandas 中翻译并高效编写
现在我们已经具备了完整 SQL 查询的大部分组件,让我们看看一个更复杂的查询在 Pandas 中的样子。
SELECT
Product,
COUNT(Order_ID)
FROM df
WHERE Order_ID IS NOT NULL
AND Purchase_Address LIKE "%Los Angeles%"
AND Quantity_Ordered == 1
GROUP BY Product
ORDER BY COUNT(Order_ID) DESC
在这里,我们在之前的查询中添加了一些内容,包括多个过滤条件以及 ORDER BY,以便查询返回的表按照我们正在聚合的度量进行排序。由于查询中有更多组件,让我们逐步看看如何在 Pandas 中实现这个查询。
首先,我们不直接在调用loc
方法时传递多个条件,而是定义一个条件列表,并将其赋值给变量FILTER_CONDITIONS
。
FILTER_CONDITIONS = [
df["Order_ID"].notna(),
df["Purchase_Address"].str.contains("Los Angeles"),
df["Quantity_Ordered"] == "1",
]
如之前所述,传递给loc
的条件应该是一个 Pandas 掩码,其结果为真或假。可以将多个条件传递给loc
,但语法应如下所示:
df.loc[condition_1 & condition_2 & condition_3]
然而,像这样直接传递条件列表是不可行的:
df.loc[FILTER_CONDITIONS]
# doesn't work -> you can't just pass a list into loc
如果你尝试上述操作会出现错误,因为每个条件应通过&
操作符(用于“且”条件)或|
操作符(用于“或”条件)分隔。相反,我们可以编写一些快速代码来以正确的格式返回条件。我们将使用functools.reduce
方法将条件组合在一起。
如果你想在笔记本中查看它的样子,并查看使用reduce
函数组合字符串的效果,可以尝试这个:
reduce(lambda x, y: f"{x} & {y}", ["condition_1", "condition_2", "condition_3"])
这将输出如下字符串:
>>> 'condition_1 & condition_2 & condition_3'
回到我们实际的 Pandas 条件,我们可以改写为(不使用字符串格式化,仅使用我们在FILTER_CONDITIONS
变量中定义的条件列表)。
reduce(lambda x, y: x & y, FILTER_CONDITIONS)
reduce
的作用是将一个函数累积地应用于一个可迭代对象中的元素,或者在我们的例子中,对FILTER_CONDITIONS
列表中的每一项运行lambda
函数,并用&
操作符将它们组合起来。这会一直运行,直到没有条件剩下,或者在这种情况下,对于所有三个条件,它将有效地返回:
df["Order_ID"].notna() & df["Purchase_Address"].str.contains("Los Angeles") & df["Quantity_Ordered"] == "1"
最后,让我们将条件列表添加到 Pandas 中以创建最终的分组查询:
final_df = (
df
.loc[reduce(lambda x, y: x & y, FILTER_CONDITIONS)]
.groupby("Product")
.size()
.sort_values(ascending=False)
)
你会注意到与之前的查询有两个额外的区别:
-
与其指定要计数的特定列,我们可以简单地调用
size
方法,它会返回 DataFrame 中的行数(就像之前每个Order_ID
值都是唯一的,并且意味着我们计数时的每一行一样); -
在 Pandas 中有几种不同的方法来进行排序,其中一种方法是简单地调用
sort_values
并传递ascending=False
以按降序排序。
如果你想使用之前的语法来聚合数据,它将如下所示:
final_df = (
df
.loc[reduce(lambda x, y: x & y, FILTER_CONDITIONS)]
.groupby("Product")
["Order_ID"].count()
.sort_values(ascending=False)
)
Pandas 代码输出 — 图片由作者提供
两种方法的输出将与之前相同,即一个 Series,其中包含你分组的列和每个产品的计数。
如果你想要输出 DataFrame,可以在系列上调用 reset_index
方法,以获取你分组的列和你聚合的列的原始列名(在这种情况下,我们按“Product”分组,并计数“Order_ID”)。
final_df.reset_index()
Pandas 代码输出 — 图片由作者提供
就这样!所有完整 SQL 查询的组件,但最终用 Pandas 编写。为了进一步优化处理数据的过程,我们可以做以下一些事情:
-
将不同的列列表(用于 SELECT 或 GROUP BY)放入它们自己的变量或函数中(这样你或用户可以随着时间的推移修改它们);
-
将合并列列表的逻辑移到自己的函数中,以便最终用户不会对
reduce
逻辑感到困惑; -
在调用
reset_index
之后,我们可以重命名输出列(如果我们在多个列上聚合,则重命名多个列),以便更清晰,例如命名为“Count_Order_ID”。
感谢你花时间阅读这篇文章!如果你还没有,请点击关注按钮,以便及时了解我更多的技术文章。此外,如果你喜欢我的内容,我会很高兴如果你通过下面的推荐链接注册 Medium。这样我可以获得你月订阅的一部分,而且你将可以访问一些只有 Medium 会员才能使用的独家功能。
[## 通过我的推荐链接加入 Medium — Byron Dolon
作为 Medium 会员,你的会员费的一部分将分配给你阅读的作者,你可以访问每一个故事…
byrondolon.medium.com](https://byrondolon.medium.com/membership?source=post_page-----6983cae8426e--------------------------------)
更多来自我: - 5 个实用技巧助你成为数据分析师 - 掌握电子商务数据分析 - 在 Pandas DataFrame 中检查子字符串 - 学习 Python 的 7 个最佳 GitHub 仓库 - 5(又半)行代码助你理解 Pandas 数据
机器学习实验的艺术
原文:
towardsdatascience.com/how-to-run-machine-learning-experiments-that-really-matter-2ba0a88bc579
5 个简单的策略帮助你从机器学习实验中获得最大收益
·发表于数据科学之路 ·阅读时间 7 分钟·2023 年 1 月 17 日
–
图片由Oudom Pravat提供,Unsplash上的照片
实验是机器学习行业的核心。我们之所以进步,是因为我们进行实验。
然而,并非所有实验都是同等有意义的。一些实验比其他实验产生更多的商业影响。然而,专注于影响的实验选择、执行和迭代的艺术,通常不会在标准的机器学习课程中涵盖。
这常常会引起很多混淆。新的机器学习从业者可能会觉得你应该把所有东西都投入到一个问题上,然后“看看哪些有效”。但事实并非如此。
需要明确的是,我不是在谈论离线和在线测试及其变体的统计数据,例如 A/B 测试。我谈论的是实验完成前后发生的事情。我们如何选择实验的对象?如果结果是负面的,我们该怎么办?我们如何尽可能高效地进行迭代?
更广泛地说,你如何从机器学习实验中获得最大的收益?这里有 5 个简单的策略供你采纳。
1 — 知道何时进行实验
作为机器学习从业者,你脑海中总会有无数个问题。比如,如果我们去掉这个特征会怎样?如果我们添加那层神经网络会怎样?如果我们使用这个声称更快的其他库会怎样?花时间的可能性是无穷的。
鉴于你的时间预算有限,你应该如何决定实验的内容?以下是一些实用的建议:
优先考虑预期收益最高的实验。 花时间充分了解现有模型并找出最大的差距:这就是你要集中精力的地方。例如,如果一个模型仅使用少量特征,那么最好的实验可能是围绕特征发现进行的。如果一个模型是简单的逻辑回归模型,那么在模型架构方面的工作可能更有前景。
不要为了了解已经知道的东西而进行实验。 在考虑启动任何实验之前,先进行研究。如果文献中对你尝试回答的问题有广泛的共识,那么你可能不需要围绕它设计实验。相信共识,除非你有强有力的理由不这样做。
在实验之前定义清晰的成功标准。 如果没有明确的成功标准,你永远不知道什么时候完成。就是这么简单。我见过太多因为实验后启动标准发生变化而未能部署的模型。通过在进行任何实验之前定义并沟通清晰的标准来避免这一陷阱。
2 — 总是从假设开始
科学实验总是从假设开始。我们首先提出假设,然后进行实验以确认或排除它。无论哪种结果,我们都获得了知识。这就是科学的运作方式。
科学假设必须是一个声明,通常含有“因为”这个词。它不能是一个问题。“哪个模型更好?”不是一个假设。
一个假设可以是:
-
“我假设 BERT 模型对于这个问题效果更好,因为单词的上下文很重要,而不仅仅是它们的频率”,
-
“我假设神经网络在这个问题上比逻辑回归效果更好,因为特征和目标之间的依赖关系是非线性的”,
-
“我假设添加这组特征会提高模型性能,因为这些特征在另一个相关的用例中也被使用”,
等等。
我常常看到人们进行大量实验并将结果呈现在长长的电子表格中,却没有明确的结论。当被问到“为什么这个数字比那个数字大?”时,答案通常是某种形式的临时猜测。这就是 HARKing,即在结果已知后进行假设。
HARKing 是科学的对立面,它是伪科学。它很危险,因为它可能导致统计偶然现象,这些结果看起来真实,但实际上只是偶然的结果(并且在实际应用中不会出现)。
科学方法 — 在实验之前制定假设 — 是防止偶然发现的最佳保护措施。
3 — 创建紧密的反馈循环
在你的机器学习流程中改变一件事应该像改变一行代码并在终端中执行提交脚本一样简单。如果复杂得多,最好先缩短反馈周期。紧凑的反馈循环意味着你可以快速测试想法,而不需要复杂的操作。
这里有一些方法来实现这一点:
-
自动化命名。 花在思考如何命名(模型、实验、数据集等)的时间,是未能实际进行实验的时间。与其试图起一些巧妙和深刻的名字,比如“BERT_lr0p05_batchsize64_morefeatures_bugfix_v2”,不如使用coolname等库来自动化命名,只需将参数直接写入日志文件中。
-
慷慨记录。 记录实验参数时,最好多记录一些。记录是便宜的,但由于记不清楚修改了哪些设置而重新运行实验是昂贵的。
-
避免笔记本。 笔记本难以版本控制、共享,并且将代码与日志混在一起,使得每次想要更改内容时都需要上下滚动。笔记本在探索性数据分析和可视化中有其用处,但在机器学习实验中,脚本通常更好:它们可以版本控制、共享,并且在代码与日志(即输入与输出)之间创建明确的界限。
-
从小做起,快速失败。 最好先在一个小的子样本数据集上进行实验。这可以让你快速获得反馈而不浪费太多时间,并且“快速失败”:如果这个想法不起作用,你会尽早知道。
-
一次只改变一件事。 如果你同时改变多个东西,你就无法知道这些改变中的哪一个导致了你看到的模型性能变化。通过一次只改变一件事来简化你的工作。
4 — 避免“新奇事物”偏见。
我经常看到人们对最新的机器学习研究论文过于兴奋,并试图将其强行应用于特定的用例。现实是,我们在机器学习生产中遇到的问题往往与机器学习研究中的问题大相径庭。
例如,像 BERT 这样的超大型语言模型在学术基准数据集如 GLUE 上取得了显著进展,GLUE 包含了许多语言学上棘手的问题。
“奖杯无法放进行李箱里,因为它太小了。是什么太小了,奖杯还是行李箱?”
然而,一个典型的商业问题可能仅仅是检测电子商务目录中包含电池的所有产品,对于这种问题,简单的词袋方法完全可以解决,而 BERT 可能是杀鸡用牛刀。
“新奇事物”偏见的解药,再次是严格遵循科学方法,并在进行任何实验之前制定清晰的假设。
“这是一种新模型”并不是一个假设。
5 — 避免实验炼狱
实验的结果可以是积极的(我们确认了假设)或消极的(我们否定了假设),这两种结果同样有价值。积极的结果改进我们的生产模型,从而提升我们的业务指标,而消极的结果则缩小了我们的搜索范围。
我看到过太多的同行陷入“实验炼狱”:实验结果是消极的(这个想法不起作用),但他们却没有结束并继续前进,而是不断尝试原始想法的不同修改,可能是因为组织压力,可能是因为“沉没成本”偏见,或者其他原因。
实验炼狱会阻碍你转向其他更有成果的想法。接受消极实验结果只是过程的一部分,并在需要时继续前进。这就是经验科学应有的进展方式。
结语:始终保持实验
总结一下,
-
知道何时实验:优先考虑预期收益最大的实验,
-
总是从假设开始:避免 HARKing 的伪科学,
-
创建紧密的反馈循环:尽可能让你快速测试想法,
-
避免“新奇事物偏见”:记住,学术问题上的成功不一定是业务问题成功的良好指标,
-
避免实验炼狱:接受消极结果是过程的一部分,并在需要时继续前进。
让我以一条建议作为结束,这条建议是我在亚马逊的科学经理曾经给我的:
最优秀的机器学习科学家的机器很少闲置。
他指的是最优秀的机器学习科学家总是有一系列他们想要进行的实验,这些实验对应于他们制定并希望测试的不同假设。每当他们的机器即将闲置(例如即将开始周末假期时),他们会在注销前从他们的待办事项中提交实验。
机器学习是一个经验性的领域。更多的实验带来更多的知识,最终带来更多的专业知识。掌握影响力巨大的机器学习实验的艺术,你就走上了成为机器学习专家的道路。
👉 关注 我,在 Medium 上查看更多我的内容。
📫* 订阅 以便直接将我的下一篇文章发送到你的收件箱中。
💪* 成为 Medium 会员 ,并解锁无限访问权限。
还在好奇如何最大化你作为机器学习专业人员的影响力吗?请查看下面链接的文章 👇
忘掉“安静退出”:3 种策略,用更少的时间创造更多的商业影响
[towardsdatascience.com
如何在 Python 中保存和加载神经网络
原文:
towardsdatascience.com/how-to-save-and-load-your-neural-networks-in-python-cb2063c4a7bd
保存和加载 PyTorch 和 TensorFlow/Keras 中的检查点和整个深度学习模型的完整指南
·发布在 Towards Data Science ·8 分钟阅读·2023 年 4 月 5 日
–
如何在 PyTorch 和 TensorFlow/Keras 中保存和加载神经网络(图片由作者绘制)
训练神经网络通常需要大量时间和计算资源。如果在投入了这些时间和计算后丢失模型,那将是非常遗憾的。
这就是为什么你应该能够根据用例在不同阶段(训练中或训练完成后)保存和加载深度学习模型的原因:
本文介绍了如何为两个主要深度学习框架保存和加载检查点和整个模型:
- PyTorch
import torch
- TensorFlow/Keras
import tensorflow as tf
from tensorflow import keras
一般来说,当加载保存的模型时,确保使用的框架版本与保存模型时使用的版本相匹配是很重要的。
print('PyTorch version:', torch.__version__)
print('TensorFlow version:', tf.__version__)
print('Keras version:', keras.__version__)
这在将模型迁移到不同的机器或环境时尤为重要。因此,在保存和版本化模型时,存储框架的版本作为元数据是很重要的。
为什么机器学习中的版本控制必须超越源代码,涵盖数据集和机器学习模型以确保可追溯性……
如何保存和加载模型检查点
检查点功能对于在训练期间指定时间保存模型非常有用。这类似于在视频游戏中保存进度。它确保你无需从头开始,可以在出现问题时从检查点恢复。
在 PyTorch 中保存和加载模型检查点
PyTorch 检查点包含以下组件[2]:
-
模型状态(权重和偏差)
-
优化器状态
-
训练步骤或纪元
-
你选择保存的任何额外信息(例如,训练配置,如优化器、指标或当前训练损失)
PyTorch 模型通常以 PyTorch 二进制格式(.pt
或.pth
)保存。虽然这两种文件扩展名之间没有区别,但开发者社区[3] 建议使用 **.pt**
文件扩展名而不是.pth
文件扩展名,因为后者与 Python 路径配置文件的文件扩展名冲突。
你可以使用以下代码片段在 PyTorch 中保存训练检查点(checkpoint_1.pt
、checkpoint_2.pt
等):
# Define your model
model = ...
optimizer = ...
criterion = ...
# Train the model
for epoch in range(num_epochs):
# Train the model for one epoch
...
# Save a checkpoint after each epoch
PATH = f'checkpoint_{epoch}.pt'
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'train_loss': train_loss,
},
PATH)
你可以使用以下代码片段在 PyTorch 中加载训练检查点(例如checkpoint_3.pt
)。确保:
-
在继续模型训练之前设置
model.train()
-
继续仅在剩余的纪元中进行训练(
for epoch in range(epoch+1, num_epochs)
)。
# Define your model
model = ...
optimizer = ...
criterion = ...
# Load a saved checkpoint
checkpoint = torch.load('checkpoint_3.pt')
epoch = checkpoint['epoch']
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
# Set dropout and batch normalization layers to train mode
model.train()
# Resume training the model for the remaining epochs
for epoch in range(epoch + 1, num_epochs):
...
在 TensorFlow/Keras 中保存和加载模型检查点
与 PyTorch 相比,TensorFlow/Keras 中的检查点仅在检查点文件(.ckpt
)中保存模型状态(权重和偏差)[6]。
你可以通过使用回调函数在 TensorFlow/Keras 中保存训练检查点(checkpoint_1.ckpt
、checkpoint_2.ckpt
等),如下所示:
# Define and compile your model
model = ...
...
model.compile(...)
# Define the checkpoint callback that saves the model's weights at every epoch
PATH = f'checkpoint_{epoch}.ckpt'
cp_callback = tf.keras.callbacks.ModelCheckpoint(
filepath = PATH,
save_weights_only = True, # If False, saves the full model
save_freq = 'epoch')
# Train the model with the checkpoint callback
model.fit(X_train,
y_train,
epochs = num_epochs,
validation_data = (X_val, y_val),
callbacks = [cp_callback])
如果你不想使用回调函数,也可以使用model.save_weights(PATH)
方法来保存模型权重。
你可以使用以下代码片段在 TensorFlow/Keras 中加载训练检查点(例如checkpoint_3.pt
)。确保仅在剩余的纪元中继续训练(num_epochs — epoch
)。
# Define and compile your model
model = ...
...
model.compile(...)
# Load a saved checkpoint
epoch = 3
model = model.load_weights(PATH = f'checkpoint_{epoch}.ckpt')
# Define the checkpoint callback that saves the model's weights at every epoch
...
# Resume training the model
model.fit(X_train,
y_train,
epochs = (num_epochs-epoch),
validation_data = (X_val, y_val),
callbacks = [cp_callback])
如何保存和加载深度学习模型
你还可以在模型训练完成时保存模型。这在你想要部署模型或推理发生在训练代码之外时非常有用。
在 PyTorch 中保存和加载整个模型
与检查点相比,PyTorch 只在模型训练完成后保存模型状态(权重和偏差)[2]。
PyTorch 模型也以 PyTorch 二进制格式( .pt
优于.pth
[3])保存。
你可以使用以下代码片段在 PyTorch 中保存训练好的模型(model.pt
)。确保保存 model.state_dict()
而不是单独保存 model
(请参见下面的替代方案)。
PATH = "model.pt"
# Define your model
model = ...
# Train the model
...
# Save the model
torch.save(model.state_dict(), PATH)
你可以使用以下代码片段在 PyTorch 中加载训练好的模型(model.pt
)。确保:
-
在加载权重之前,创建一个相同的模型实例
-
在使用模型进行推理之前,设置
model.eval()
# Define your model architecture
model = ...
# Load the saved model parameters into your model
model.load_state_dict(torch.load(PATH))
# Set dropout and batch normalization layers to evaluation mode before running inference
model.eval()
# Use the model for inference
# ...
或者,你可以如下所示保存整个模型:
PATH = "model.pt"
# Define your model
model = ...
# Train the model
...
# Save the model
torch.save(model, PATH)
使用这种方法,你不需要在加载权重之前定义模型。
# Load the saved model parameters into your model
model = torch.load(PATH)
# Set dropout and batch normalization layers to evaluation mode before running inference
model.eval()
# Use the model for inference
# ...
然而,不推荐这种方法: 这种方法不保存模型类本身。相反,它保存了包含类的文件的路径。因此,使用这种方法保存模型可能会导致在其他项目或不同源代码中重用模型时出现问题。
在 TensorFlow/Keras 中保存和加载整个模型
Keras 模型包含以下组件 [5, 6]:
-
模型架构包括优化器及其状态、损失和
saved_model.pb
中的指标 -
模型的状态(权重和偏置)在
variables/
目录中。 -
模型的编译信息
模型可以保存为以下文件格式 [5]:
-
TensorFlow SavedModel 格式 — 推荐和默认格式,当没有指定其他文件扩展名时使用。
-
HDF5 格式(
.h5
)——一种较旧且轻量的替代方案,不保存外部损失和指标
你可以使用以下代码片段在 TensorFlow/Keras 中保存训练好的模型(model
):
PATH = "model" # Will save the model in TensorFlow SavedModel format
# Define and compile your model
model = ...
...
# Train the model
...
# Save the model
model.save(PATH)
你可以使用以下代码片段在 TensorFlow/Keras 中加载训练好的模型(model
)。
# Load the saved model
model = keras.models.load_model(PATH)
# Use the model for inference
# ...
最佳检查点选择
最佳检查点选择是一种深度学习技术,它在训练期间监控验证指标(没有提前停止),并使用具有最佳验证指标的检查点进行推理。
在最近的 关于中级深度学习技术的文章 中,我们回顾了目前似乎没有关于最佳检查点选择的最佳实践的普遍理解。虽然 深度学习调优手册 [1] 推荐使用最佳检查点选择,但 Kaggle 大师们不推荐此方法,因为这种技术往往使模型过度拟合验证集 [4]。
针对计算机视觉和自然语言处理的深度学习模型微调的实用指南
towardsdatascience.com
尽管如此,我们将介绍如何将最佳检查点选择应用于你的深度学习管道。
PyTorch 中的最佳检查点选择
保存 类似于 在 PyTorch 中保存模型检查点,但有一些变化:
-
我们 仅保存模型,但不保存训练信息,如 epoch、优化器状态等,因为我们不打算继续训练此模型。
-
在训练过程中手动添加验证指标的监控
# Define your model
model = ...
optimizer = ...
criterion = ...
# Train the model
for epoch in range(num_epochs):
# Train the model for one epoch
...
if (best_metric < current_metric):
best_metric = current_metric
# Save a checkpoint after each epoch
PATH = f'checkpoint_{epoch}.pt'
# Save the model
torch.save(model.state_dict(), PATH)
加载 等同于 在 PyTorch 中加载整个模型。
TensorFlow/Keras 中的最佳检查点选择
保存 类似于 在 TensorFlow/Keras 中保存模型检查点,但有一些变化:
-
使用
save_weights_only = True
保存整个模型 -
从
PATH
中移除.ckpt
以将模型保存为 SavedModel 格式 -
添加
monitor
和save_best_only
参数
# Define and compile your model
model = ...
...
model.compile(...)
# Define the checkpoint callback that saves the model's weights at every epoch
PATH = f'./checkpoints/checkpoint_{epoch}' # Remove .ckpt to save as SavedModel format
cp_callback = tf.keras.callbacks.ModelCheckpoint(
filepath = PATH,
monitor = "val_acc", # Metric to monitor for best checkpoint picking
save_best_only = True,
save_weights_only = True,
save_freq = 'epoch')
# Train the model with the checkpoint callback
model.fit(X_train,
y_train,
epochs = num_epochs,
validation_data = (X_val, y_val),
callbacks = [cp_callback])
加载 等同于 在 TensorFlow/Keras 中加载整个模型。
总结
本文回顾了在深度学习框架 PyTorch 和 TensorFlow/Keras 中保存和加载神经网络的不同用例。下文展示了 PyTorch 和 Keras 之间的比较概述。
PyTorch 与 TensorFlow/Keras 中保存深度学习检查点或模型时的概述(图像由作者提供)
-
模型架构: 在 PyTorch 中,模型架构从未保存,因此还必须通过某些源代码版本控制进行保存。在 TensorFlow/Keras 中,当你保存整个模型时,模型架构会被保存。
-
模型权重: PyTorch 和 TensorFlow/Keras 都可以仅保存模型权重。然而,在 PyTorch 中,这是在保存最终训练模型时完成的,而在 TensorFlow/Keras 中,这适用于检查点保存。
喜欢这个故事吗?
免费订阅 以便在我发布新故事时收到通知。
[## 每当 Leoni Monigatti 发布时接收电子邮件。
每当 Leoni Monigatti 发布时接收电子邮件。通过注册,如果你还没有,你将创建一个 Medium 账户……
medium.com](https://medium.com/@iamleonie/subscribe?source=post_page-----cb2063c4a7bd--------------------------------)
在 LinkedIn,Twitter* 和* Kaggle上找到我!
参考资料
[1] V. Godbole, G. E. Dahl, J. Gilmer, C. J. Shallue 和 Z. Nado (2023). 深度学习调优手册 (第 1.0 版) (访问日期:2023 年 2 月 3 日)
[2] M. Inkawhich for PyTorch (2023). 保存和加载模型 (访问日期:2023 年 3 月 27 日)。
[3] kmario23 在 Stackoverflow (2019)。PyTorch 中 .pt、.pth 和 .pwf 扩展名之间有什么区别?(访问日期:2023 年 3 月 27 日)。
[4] P. Singer 和 Y. Babakhin (2022)。深度迁移学习实用技巧 于 2022 年 11 月在 Kaggle Days Paris 上介绍。
[5] TensorFlow (2023)。指南:保存和加载 Keras 模型(访问日期:2023 年 3 月 27 日)。
[6] TensorFlow (2023)。教程:保存和加载模型(访问日期:2023 年 3 月 27 日)。