BigQuery 中的新生成 AI 功能
原文:
towardsdatascience.com/the-new-generative-ai-function-in-bigquery-38d7a16d4efc
如何使用 BigQuery 的 GENERATE_TEXT 远程函数
·发表于数据科学之路 ·阅读时间 10 分钟·2023 年 12 月 1 日
–
“每个人都可以通过 SQL 知识和良好的提示结构在 BigQuery 中进行编程和自然语言处理分析” [照片来源:Adi Goldstein于Unsplash]
介绍
自从我开始使用 Google 平台以来,Google 一直没有停止用其BigQuery(BQ)功能和开发给我带来惊喜。
四年前我经历了真正的“哇”时刻。
我记得就像昨天一样,当时我坐在大数据伦敦2019 年会议的前排。那时我完全不知道只用 BQ 函数就能创建机器学习模型的可能性,或者更准确地说,不知道 BQ 机器学习(BQML)是什么。
至少在会议环节之前,Google 的同事展示了如何仅使用 Google 的 SQL 创建分类、聚类和时间序列预测模型。
当时我脑海中闪过的第一个念头是“你一定是在开玩笑”。
我脑海中的第二个念头是:“这是否意味着所有只知道 SQL 的人都能创建机器学习模型?”
正如你可以想象的那样,如果你使用 BigQuery 作为数据仓库,那么答案是“是的”。
现在,在使用了BQML函数一段时间后,上述问题的正确答案是“也许”。
这意味着即使[CREATE MODEL](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create)
语法是用 SQL 编写的,仍然需要了解机器学习建模和统计学知识。
换句话说,你仍然需要理解不同类型机器学习用例的模型背后的数学知识(监督学习/无监督学习),进行特征工程、超参数调整和模型评估任务。
快进到 2023 年,BigQuery 通过其新功能进一步让我惊叹。
这一次,我们讨论的是新的生成性 AI BigQuery 机器学习函数。
利用这些新功能,数据工程师和分析师可以在 BQ 表中存储的文本数据上执行生成性自然语言任务,只需几行查询代码。
因此,本博客文章的目标是展示 BQ 在生成性 AI 中的新分析进展,重点介绍一个功能**—** GENERATE_TEXT功能。
关于 GENERATE_TEXT 函数
GENERATE_TEXT函数的主要思想是通过 BigQuery 中的 SQL 和提示指令来协助数据专业人员完成以下任务[1]:
-
分类
-
情感分析
-
实体提取
-
提取式问答
-
总结
-
以不同风格重写文本
-
广告文案生成
-
概念构思
换句话说,该函数可以利用Vertex AI [text-bison](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text)
自然语言基础模型 [1]在 BQ 中对存储的文本属性执行生成性自然语言任务。
它的工作原理如下[1]:
-
首先,它向代表 Vertex AI
text-bison
自然语言基础模型(LLM)的 BigQuery ML 远程模型发送请求。 -
然后,它返回带有定义输入参数的响应。
这意味着不同的函数参数和输入提示设计(分析的提示指令)会影响 LLM 的响应。
说到这里,可以将以下函数特定参数传递给 GENERATE_TEXT 函数,并影响响应质量 [1]:
#1: model
[STRING
] → 指定使用 text-bison
Vertex AI LLMs 之一的远程模型的名称。
#2: query_statement
[STRING
] → 指定用于生成提示数据的 SQL 查询。
#3: max_output_tokens
[INT64
] → 设置模型输出的最大 token 数量。对于较短的响应,可以指定较低的值。
- 注意: 一个 token 可能比一个词小,约为四个字符。
#4: temperature
[FLOAT64
] → 范围在[0.0,1.0]
内的参数,用于在响应生成期间进行采样,当 top_k
和 top_p
被应用时会发生采样。
- 注意: 该参数表示 token 选择的随机程度,即,较低的值适用于需要更确定性响应的提示,而较高的值可能导致更具多样性的结果。
#5: top_k
[FLOAT64
] → 范围在[1,40]
内的参数,决定模型如何选择输出的 token。
- 注意: 为了获得更少的随机响应,应指定较低的值。
#6: top_p
[FLOAT64
] → 范围在[0.0,1.0]
内的参数,决定模型如何选择输出的 token。
- 注意: 为了获得更少的随机响应,应指定较低的值。 Tokens 从最可能的(基于
top_k
值)到最不可能的进行选择,直到它们的概率总和等于top_p
值。
在了解了函数的目的和每个参数的作用之后,我们可以开始演示如何使用 BigQuery 的 GENERATE_TEXT 函数。
使用该函数的 4 步方法
本节将介绍测试生成 AI 功能GENERATE_TEXT的四个方法步骤。
GENERATE_TEXT 函数工作流程 [图片由作者提供]
总的来说,该方法包括:
-
使用 ChatGPT 生成小型模拟数据集并将其导出到 Google Sheets 文档中。
-
在 Google Sheets 文档的基础上创建 BigQuery 数据集。
-
设置 Google 服务 Vertex AI 和 BQ 之间的连接,以便使用远程生成 AI 模型。
-
在 BigQuery 的模拟数据集上测试 GENERATE_TEXT 函数的实际用例示例。
每一步的更多背景信息请参见下面的子节。
步骤 1: 使用 ChatGPT 生成模拟数据集
由于我没有现实生活中的数据集,我决定创建一个用于测试目的的模拟数据集。
为此,我使用 ChatGPT 输入了以下提示文本:
“我需要为 50 种不同的虚构头发产品自动生成客户评价。
对于每个产品,我需要 2 条正面评论、2 条负面评论和 1 条中性评论。
此外,我希望评论至少有 4 句话,并包含不同的信息:产品名称、购买产品的商店位置和产品价格。
在负面评论中,请包括不同的原因,如产品质量问题、价格问题和配送问题。
结果是一个小型数据集表,包含五个属性(下图预览):
-
product_name — 包含虚假产品名称的值,
-
review_type — 包含评论情感类型(正面、负面或中性),
-
store_location — 包含随机的城市和州名称,
-
product_price — 包含随机的美元产品价格***,*** 和
-
product_review— 包含五句话长的虚假产品评论文本。
BigQuery 模拟数据集预览 [作者提供的图片]
完整数据集可以在 Git 仓库中找到 这里 [4]。
在准备好模拟数据集并将其存储在 Google Sheets 后,下一步是将其转移到 BigQuery。
步骤 2: 在 Google Sheet 文档上创建 BQ 表
当我有一个小型数据集,需要频繁手动调整或更改时,我的首选方法是创建一个 BigQuery 表,基于可编辑的 Google Sheet 文档。
为此,需要以下子步骤 [2]:
子步骤 #1: 在 BigQuery 项目中创建数据集和表,通过指定文件位置和格式
应选择 BigQuery 环境右上角的 CREATE TABLE
选项来创建新数据集和表。需要输入标有星号(*******) 的值,如下图所示。
在 Google Sheets 中的模拟数据集上创建 BQ 数据集和表 [作者提供的图片]
从上图可以看出,新创建的 BQ 数据集名称为 hair_shop
,新创建的表名称为 product_review_table
。
需要注意的重要事项:
- 如果你不想在上图所示的
Schema
部分定义表模式,则从 Google Sheets 导入的所有属性默认的数据类型为STRING
。
子步骤 #2: 在 BQ 中查询模拟数据集
第二个子步骤是直接在 BigQuery 中探索 hair_shop.product_review_table
数据集。
在 BQ 中查询模拟数据集 [作者图片]
现在你可以访问 BQ 中的模拟数据集,是时候将生成 AI 远程模型连接到它了。
步骤 3: 将 Vertex AI 服务连接到 BQ
详细说明该步骤的指南可以在 Google 的文档 这里 [3] 中找到。
总结提供的 Google 教程,将 Vertex AI 连接到 BQ 的三个主要子步骤是:
-
子步骤 #1: 从 BigQuery 创建一个云资源连接,以获取连接的服务帐户。
-
子步骤 #2: 授予连接的服务帐户适当的角色,以访问 Vertex AI 服务。
-
子步骤 #3: 创建代表托管 Vertex AI 大型语言模型的
text-bison
远程模型,在创建的 BigQuery 数据集hair_shop
中。
一旦这些子步骤完成,新的对象 Model
将在 hair_shop
数据集中可见。
选定 BQ 数据集中 Model
的新数据库对象的预览 [作者图片]
最后,魔法可以开始了,我们可以开始使用 GENERATE_TEXT 函数。
步骤 4: 使用 *GENERATE_TEXT*
函数在 BQ 中
在此步骤中,我们将关注两个用于测试函数使用的用例:情感分析 和 实体提取。
选择这两个用例的原因是我们已经在输入模拟数据集中创建了两个属性(review_type
和 product_review
),可以用来测试函数结果。函数结果指的是验证 AI 生成的值在新属性中的准确性。
现在让我们展示生成每个用例的新属性的具体输入输出流程。
情感分析示例
情感分析的查询显示在下面的代码块中。
--Generate the positive/negative sentiment
SELECT
ml_generate_text_result['predictions'][0]['content'] AS review_sentiment,
* EXCEPT (ml_generate_text_status,ml_generate_text_result)
FROM
ML.GENERATE_TEXT(
MODEL `hair_shop.llm_model`,
(
SELECT
CONCAT('Extract the one word sentiment from product_review column. Possible values are positive/negative/neutral ', product_review) AS prompt,
*
FROM
`macro-dreamer-393017.hair_shop.product_review_table`
LIMIT 5
),
STRUCT(
0.1 AS temperature,
1 AS max_output_tokens,
0.1 AS top_p,
1 AS top_k));
查询的详细信息如下:
外部 SELECT 语句:
-
外部查询从 LLM 获取的新生成的字符串属性
review_sentiment
通过将ML.GENERATE_TEXT
函数应用于hair_shop.llm_model
对象来选择。 -
此外,它还选择了输入数据集
hair_shop.product_review_table
中的所有其他列。
内部 SELECT 语句:
-
内部查询语句选择了一个
CONCAT
函数,并对每个来自输入数据集hair_shop.product_review_table
的product_review
应用特定的提示指令。 -
换句话说,提示指令引导模型从
product_review
属性中提取一个单词的情感(积极、消极或中立)。
模型参数的 STRUCT:
-
temperature: 0.1
- 较低的值 0.1 将导致更可预测的文本生成。 -
max_output_tokens: 1
- 将模型的输出限制为 1 个标记(情感分析结果可以是积极的、消极的或中性的)。 -
top_p: 0.1
影响下一个标记的可能性分布。 -
top_k: 1
限制考虑的前几个标记的数量。
在触发所示查询后,对前五个结果进行了分析:
GENERATE_TEXT 函数的情感分析结果 [图片由作者提供]
从图像中可以看到,新生成的属性review_sentiment
被添加到模拟数据集中。review_sentiment
属性的值与review_type
属性的值进行了比较,并且它们的记录匹配。
在情感分析结果之后,目标是识别每个情感背后的原因,即进行实体提取。
实体提取示例
实体提取分析的查询在下面的代码块中显示。
--Check what's the reason behind the positive/negative sentiment
SELECT
ml_generate_text_result['predictions'][0]['content'] AS generated_text,
* EXCEPT (ml_generate_text_status,ml_generate_text_result)
FROM
ML.GENERATE_TEXT(
MODEL `hair_shop.llm_model`,
(
SELECT
CONCAT('What is the reason for a review of the product? Possible options are: good/bad/average product quality, delivery delay, low/high price', product_review) AS prompt,
*
FROM
`macro-dreamer-393017.hair_shop.product_review_table`
LIMIT 5
),
STRUCT(
0.2 AS temperature,
20 AS max_output_tokens,
0.2 AS top_p,
5 AS top_k));
查询的分解与情感分析查询的分解类似,只是提示指令和模型参数值已调整为实体提取分析。
在触发所示查询后,对前五个结果进行了分析:
GENERATE_TEXT 函数的实体提取分析结果 [图片由作者提供]
review_reason
属性的结果与product_review
属性的值进行了比较。与之前的示例一样,结果匹配。
各位,这就是了。这个教程展示了如何仅通过使用 SQL 和提示文本从非结构化文本中创建新属性的两个用例。所有这些都借助 BigQuery 中新生成的 AI 功能完成。
现在,我们可以分享这篇博客文章的简要总结。
结论
这篇博客文章的目标是介绍 BigQuery 中新的GENERATE_TEXT
ML 功能的使用。
通过此功能,SQL 导向的数据专业人士现在可以从直接存储在 BQ 数据仓库表中的非结构化文本中创建新的洞察(属性)。
换句话说,该功能使数据专业人士能够直接在 BigQuery 环境中利用NLP 机器学习任务。
此外,它弥合了使用 Python 开发新分析洞察的数据专业人士与那些更注重数据库的专业人士之间的知识差距。
最后,我将以一句话总结这篇博客文章:“每个人都可以使用 SQL 知识和良好的提示结构在 BigQuery 中进行编码和执行 NLP 分析。”
知识参考
-
[1] Google 文章《ML.GENERATE_TEXT 函数》,访问时间:2023 年 11 月 10 日,
cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-text
-
[2] Supermetrics 文章《Google Sheets 到 BigQuery:逐步指南》,访问时间:2023 年 11 月 22 日,
t.ly/ZZ2lN
-
[3] Google 教程《使用 ML.GENERATE_TEXT 函数生成文本》,章节《创建连接》,访问时间:2023 年 11 月 10 日,
cloud.google.com/bigquery/docs/generate-text#generate_text
-
[4] 作者的 Git 代码库:
github.com/CassandraOfTroy/bigquery-generate_text-AI_function/tree/main
数据团队面临的下一个大危机
数据团队比以往任何时候都更为重要 —— 但他们需要更贴近业务。以下是我们如何纠正航向的方法。
·
关注 发布于 Towards Data Science ·10 分钟阅读·2023 年 4 月 17 日
–
图片由 Daniel Lerman 提供,来自 Unsplash。
过去十年间,数据团队既像在水下又像乘风破浪。
我们一直在建设现代化的数据堆栈,像生命取决于它一样迁移到 Snowflake,投资无头 BI,并比你说反向 ETL 更快地扩展我们的团队。然而,我们大部分时间不知道这些工具是否真正为业务带来价值。
别误解我的意思:我坚信现代数据堆栈。当涉及到快速高效地生成分析时,云原生、基于 SQL 和模块化是最佳选择。然而,在如今紧张的预算和精简的团队面前,仅仅引用云的弹性和速度还不足以证明对这些工具的投资是合理的。
现代数据堆栈菜单。图片由 Matt Turck 和 Firstmark Capital 提供。
现在,当服务员递上账单时,CFO 不再毫不犹豫地掏出信用卡,而是逐项调查账单内容。
对于数据团队来说,账单已经到期。应该是机会的事情变成了危机,因为组织发现他们离业务还不够近,无法解释为何他们点了龙虾。
我们是如何到达这里的?
数据团队以几乎没有问题的方式首开历史,建立了 8 位数的技术堆栈。
在 2010 年代中期,数据领导者被赋予了“成为数据驱动”的任务——不管这意味着什么。当然,数据可以帮助优化成本、改善财务表现、指导产品路线图、提供出色的客户洞察并获得竞争优势。但“数据驱动”是一个模糊的目标,带有模糊的指标和不清晰的投资回报。
当然,数据团队并没有失控。在“增长不惜一切代价”的时代,火车全速前进。高管和决策者们看到谁在胜出——谷歌、Netflix 和亚马逊等公司——投资数据似乎是显而易见的选择。
我们这个新兴行业构建了堆栈,这些堆栈要么是在内部拼凑而成,要么是临时购买的,用以解决特定问题。无论这些系统——以及数据本身——是否与期望的、软件工程启发的五个九的可靠性相结合,通常都是事后考虑的。
在这个阶段,拥有数据往往就足以推动增长。有些数据有用,有些数据则没有,但至少我们拥有了它。到 2020 年,随着一切变得数字化并开始产生数据,疫情更是火上浇油。
像 Snowflake、Databricks 和 Fivetran 这样的技术出现,仿佛魔法一般,解决了许多“成为数据驱动”的问题。更快的洞察?没错!更简单的摄取?没错!更智能的模型?没错!
然而,最终这些解决方案开始将数据量与成本挂钩。快进到今天,你的数据每年快速增长,你面临的是 1000 倍的数据量和 1000 倍的成本。在这个市场中,这无疑是一颗难以下咽的药丸。
要想脱颖而出并证明我们的工作,提供数据是不够的。它还需要可靠且有明确目的。
换句话说,数据团队需要更接近业务。
那么我们该如何实现这一目标呢?我有一些想法。
通过了解人们来了解业务
谁不喜欢一只可爱的填充企鹅?照片由Andrea Gradilone拍摄,来源于Unsplash
你是否曾根据你知道的一个随机事实(例如,他们喜欢企鹅)为多年未见的人(例如,老朋友或远房亲戚)购买过礼物?
如果答案是肯定的,你绝对不是唯一的。数据团队就像一个远方的朋友送来的看似贴心的礼物一样,希望通过丰富的洞察来正确地对待他们的利益相关者,从而改善他们的工作并为业务带来价值。但你不能假装对数据有同理心。
如果你没有深刻理解消费者的需求,你的报告和分析就像是一只 5 英尺的填充企鹅一样没什么价值。
数据领导者在推动价值时应该做的第一件事是与他们的消费者和业务利益相关者交谈。这是显而易见的建议,但“需求收集”的任务通常被委托给分析师或嵌入式团队。这种做法有一定效果,但正如任何玩过传话游戏的人都能告诉你一样。
例如,Red Ventures 的数据工程总监 Brandon Beidel 每周与每个业务团队会面,以更好地了解他们的用例并制定信息充分的数据服务水平协议。
他说:“我总是用简单的业务术语来框定对话……我会问:
-
你如何使用这张表格?
-
你什么时候查看这些数据?你什么时候报告这些数据?这些数据需要是实时的、每小时的还是每天的?
-
这有什么用途?
-
如果数据延迟,谁需要被通知?”
这种实操型领导的优势是什么?你可以塑造参与度。
“如果有人告诉我数据很重要,但没人能告诉我数据是如何使用的,我也会提出质疑。对话变得更加复杂,我甚至会得到可以迅速转化为查询的描述,比如‘这一列中没有空值’,” Brandon 说。
你还可以像产品团队发起 NPS 调查一样,大规模调查你的数据消费者,这是一种 JetBlue 数据团队在最近的网络研讨会中讨论的策略。
创建异步反馈循环
你不能总是实时与每个人交谈。异步沟通和反馈循环对于数据和业务对齐(尤其是在今天的远程工作环境中)至关重要。
如果你没有一个广泛可访问且活跃的 Slack 频道用于这些类型的沟通,考虑立即创建这样一个沟通空间。这也是数据与分析总监 Priya Gupta 在快速增长的初创公司 Cribl 创建数据文化的关键之一。她说:
“我们的数据团队倾向于过度沟通,我们尽可能通过多种渠道进行交流。正如我们作为数据工程师会对那些文档不全的数据源心存疑虑一样,业务用户对沉默的数据团队也会天然感到怀疑。
类似 Slack 或 Teams 这样的聊天应用在这方面非常有帮助。我们创建了一个集中请求的频道,这有两个目的。它让整个分析团队能够看到请求情况,同时也让其他相关方了解他们的同行感兴趣的内容。
“你见过的最令人满意的事情就是当有人在你的公共 Slack 频道中提出请求时,另一个相关方回答了原本应该由你回答的问题。”
但也许最重要的反馈环节是你如何向消费者展示数据产品的可信度和可靠性。并非所有的数据产品都会或需要 100%可靠。
一旦你创建了自助服务或发现机制,进一步展示可靠性 SLA 以及产品满足该 SLA 的时间百分比。这正是Roche在其数据网格上构建数据产品时采用的策略。
站在数据使用者的角度思考
你可能甚至不需要完全站在他们的立场上……也许只需要几份仪表板的长度?照片由Jose Fontano提供,来源于Unsplash
如果你感到雄心勃勃,下一步就是从空谈转向实践。毕竟,业务相关方在数据方面不总是了解可能性。
类似于亨利·福特说消费者会要求他提供更快的马,有时候数据消费者只会要求一个更快的仪表板。我们需要开始生成自己的假设,确定需要关注的地方、需要寻找的内容以及如何应用这些洞见,而不是依赖业务相关方告诉我们什么重要。
为此,让数据团队的成员与相关方深入接触,体验他们的数据生活。
这是策略,由 Upside 的分析工程团队制定,经过高级数据分析工程师 Jack Willis 的经验 “…意识到很多为这个团队制作的[数据]产品在我实际看到他们的使用方式时并没有达到预期。”
他们的数据赋能框架包括三个步骤:完全融入团队中,与利益相关者一起规划,并培训团队以确保可持续的所有权。这是一种有效的方法,可以最大限度地发挥像分析工程师这样的新角色的价值,他们能够弥合数据工程师和分析师之间的差距。他们可以更深入地了解业务运作以及哪些数据真正能带来变化。
“我们在数据产品中建立了一条信任之路……我们的利益相关者不再害怕数据和工程,我们的数据从业者也不再害怕业务……这让我们进入了一个数据飞轮,”Jack 说道。
让采纳成为你的指南
你不总是需要生活在数据消费者的世界中才能理解他们的故事。这个故事也可以通过他们采纳的东西和不采纳的东西来讲述。实际上,*“暗数据”*和数据孤岛可能比被广泛采纳的数据产品更具信息性。
我们是否应该将路径移到那里?图片由作者提供。
如果V_GOOD_DASHBOARD_48
是由你的业务运营团队导出到 Excel 中的版本,它比V_GOOD_DASHBOARD_49
更有价值(即使知道你的 Looker 技能被低估可能会让你感到痛苦)。
在你从客户数据平台过渡到更具云原生的解决方案之前,了解营销团队如何使用它以及原因是很重要的。与提供强大的客户细分功能一样,直观的自助访问也可能同样有价值。
为此,数据团队需要投资于能够揭示谁在使用什么数据及其使用方式的方法和技术。
但现代数据堆栈本身并不会使数据团队对其数据有更大的可见性,也不会增加数据团队和利益相关者之间的透明度。如果有的话,现代数据堆栈可能通过提供更大的访问权限而带来更少的清晰度和背景,从而使这一已经脆弱的关系更加复杂。
我们需要一种方法,将上游表和下游资产连接起来,这种方法应该涵盖你数据环境的全部——不仅仅是数据仓库、数据湖或转换层。我们需要一种真正端到端的方法,一直到消费层。
在今天的时代,无知并非幸福,而是失败的根源。对我们的数据资产有可见性和透明度将帮助我们优先考虑,保持专注,并真正推动业务的进步。
创建一个语义层
我们的行业正在进步,正在对语义层进行规范化,且自助数据平台正在将更多权力交给分析师,使他们能够以前未曾有过的方式深入处理数据。
在没有进行深入讨论业务如何思考和使用数据的情况下,几乎不可能创建语义层,有时也称为指标层。
当你正在定义“账户”的普遍定义,并与业务利益相关者讨论是否应该包括免费用户时,你正在深入探讨并巩固你业务的真正驱动因素。
语义层可以成为你进行从未进行的讨论的绝佳借口,讨论你似乎应该知道的点。你的高管团队可能也有同感。他们也不真正理解“账户”是什么意思。
随着真正的语义层开始形成,帮助你开发和标准化北极星指标的解决方案,如增强分析平台或数据笔记本,可以提供一个很好的过渡方案。
专注于重要的短期胜利
处理临时请求、调查断裂的数据管道,以及回应财务部 Bob 提出的第五个问题都是快速胜利,但它们并不能实质性地推动进展。
另一方面,启动多年的公司范围计划在启动电话会议之前往往就注定失败了。大型“资本 P”项目(数据网格,如何?)虽然重要且值得,但它们不需要“完成”才有用。最好的选择是专注于具有明确业务价值的小型短期胜利。
再次强调,采纳应该是你的指导方针。将大部分资源集中在优化和构建关键用例和数据资产上(你知道你的关键数据资产吧?)。
理解关键资产的使用情况可以帮助你专注于对业务利益相关者真正重要的数据。图片由作者提供,来自内部数据平台。
想提高数据可信度?专注于一个与最大影响相关的数据小集合,例如客户行为或第三方营销数据。
-
按领域对数据进行分段,以便知道当出现问题时,哪个业务部分负责。
-
尽量减少在新鲜度、体量和模式检查(你知道,就是那些简单的)的时间,然后专注于编写自定义规则以捕捉分布或字段健康异常。
-
启动 Slack、Teams 或信鸽渠道,以便在数据出现问题时提醒所有相关方,并在数据血统的帮助下,提供受影响的报告和仪表板的建议。
缩小你的关注范围是否意味着你无法让所有人满意?是的。缩小关注范围是否意味着你在本季度会更成功?是的。
危机解除了吗?
解决我们下一个重大危机不会一蹴而就。像任何类型的变革一样,将我们的工作更紧密地与业务结合需要时间、无情的优先排序和洞察我们能创造最大价值的能力。
就我个人而言,随着我们采纳更多流程、技术和团队结构,将数据团队从风暴中引向平静的水域,我对未来的展望充满了兴奋。
那么,你的团队将如何应对我们下一个重大危机?我在倾听。
请联系 Barr 在 LinkedIn 上 倾诉你的挫折、发送数据梗图或分享你在应对这次危机中的经历。始终乐于交流。
下一步是负责任的人工智能。我们如何实现这一目标?
原文:
towardsdatascience.com/the-next-step-is-responsible-ai-how-do-we-get-there-ecce929a1c03
机器学习解决方案在我们的生活中占据了重要地位。现在不仅仅是关于性能,还有关于责任。
·发表于Towards Data Science ·阅读时间 12 分钟·2023 年 8 月 26 日
–
照片由Jude Infantini拍摄,发布在Unsplash
在过去几十年中,许多人工智能项目专注于模型效率和性能。结果记录在科学文章中,最佳性能的模型被部署在组织中。现在是将另一个重要部分纳入我们的人工智能系统的时候了;责任感。这些算法将继续存在,如今每个人都可以通过 chatGPT、co-pilot 和提示工程等工具接触到它们。现在更具挑战性的部分包括道德咨询、确保谨慎委托和告知利益相关者。这些实践共同促进了负责任和伦理的人工智能环境。在这篇博客文章中,我将描述在人工智能项目中责任感的含义,以及如何通过 6 个实用步骤将其融入项目中。
负责任的人工智能简要介绍。
在我深入探讨负责任的人工智能(rAI)之前,让我首先概述一下数据科学领域中采取的一些重要步骤。在之前的博客中,我写过关于数据科学中需要学习的内容 [1],以及数据科学产品如何可以增加收入、优化流程和降低(生产)成本。目前,许多部署的模型在性能和效率方面得到了优化。换句话说,模型应具有高预测准确性和低计算成本。然而,更高的模型性能通常会伴随模型复杂度的逐渐增加。一些模型被称为“黑箱模型”。例如,在图像识别和文本挖掘领域,神经网络使用特定的模型架构在数亿个参数上进行训练。理解这些模型为何做出特定决策变得困难甚至不可能。另一个例子是在金融领域,许多核心流程依赖于算法,机器每天都会做出决策。最重要的是,当需要时,这些机器做出的决策必须能够由人工进行事实核查和重新评估。
为了揭开黑箱模型,一个新的数据科学领域应运而生,这就是可解释人工智能,简称xAI。新的 xAI 算法阐明了模型的决策,并帮助研究人员和数据科学家解释模型为何做出特定的决策。让我们进入下一部分,看看为何负责任的行为至关重要!
新技术使人工智能变得更加可及。
尽管机器学习解决方案已经存在多年,但我们仍然处于一个新纪元的初期,这一纪元中算法被应用于各种过程和领域,从金融到医学。迄今为止,只有少数数据科学家创建、使用和部署这些模型。那些学习过数学/统计学和其他精确学科的人知道如何训练模型和编写编程代码。这种情况将发生剧烈变化,因为我们正处于一个几乎每个拥有互联网连接的人都可以访问各种算法的时代。技术如 chatGPT、co-pilot 和提示工程,加上生成性人工智能的发展,开始让复杂的数据科学世界变得可及。这意味着没有统计知识和编程技能的个人现在也可以创建模型,而不需要理解其具体工作原理。因此,我们需要制定一些规则,因为创建和部署机器学习模型不是一件可以轻视的事情[6]。
创建和部署机器学习模型不是一件可以轻视的事情!
如果我们回顾算法发展的历程,最初是数学家和统计学家创建了核心基础算法 (#1)。然后是学术机构的科学家们优化/改进和/或扩展了核心基础 (#2)。接着是科学程序员将包含数学方程式的科学文章翻译成应用程序 (#3)。仅仅十年前,所谓的“数据科学家”出现了,这基本上是前面三组的混合体,我们现在可以称之为基础数据科学家 (#4a)。随后,第二组数据科学家出现了;他们专注于应用,也被称为应用数据科学家 (#4b)。由于算法和方法需要被部署和维护,新的开发者群体,如(数据)工程师,也逐渐进入这个领域 (#5)。直到现在,处理复杂机器学习模型的群体仍然是专业的科学家、开发者和程序员。
很快,在数据科学领域将会出现一个新群体:那些没有统计学/编程经验但拥有足够技术知识,通过依赖副驾驶和类似 chatGPT 的技术来创建和部署人工智能产品的人。随着时间的推移,也许更多的非技术人员也会创建机器学习模型,因为基本上,你只需要一条连接互联网的线路和最新的技术。
我们正处于一个只需要互联网连接就能创建人工智能模型的阶段。
新的大型语言模型 (LLMs) 是一个技术和革命性的进步,但我们也需要考虑可能的风险和后果。如果我们回顾开发者的发展轨迹,可以清楚地看到,在每个步骤中,越来越多的人可以使用复杂的人工智能方法。然而,存在一个权衡:在每个步骤中,个人对底层算法和/或编程代码的具体工作机制变得越来越不知情。
在未来几年,数据和算法的使用可能会呈指数增长。我们正处于一个需要制定规则和最佳实践的阶段,以便负责任地使用最新的技术。
负责任的人工智能的六个部分。
当我们谈论*负责任的人工智能 (rAI)*时,这不仅仅是我们需要完成或关注的单一任务。与人工智能打交道时的责任意味着我们需要在项目的所有(非)技术步骤中仔细考虑我们所做的决定。作为数据科学家,我们可能需要在数据收集、模型训练、输出和可视化模型结果的过程中做出数十到数百个决策。然而,作为数据科学家的责任甚至在数据收集之前就开始了,因为你可能需要问这样一个问题:为什么需要使用选定的技术?它的目的是什么,它打算产生什么结果?
数据科学家的责任甚至在数据收集之前就开始了。
如果对这些问题的回答仍然关乎人工智能的必要性,那么我们需要保护基本权利,如隐私,包括治理,并注意数据输入质量、模型质量和输出质量。负责任的人工智能可以总结为六个部分,其中伦理贯穿所有部分:隐私、治理、输入质量、模型质量和输出质量。在接下来的章节中,我将更详细地描述这六个主题。
作者创建的图像。
1. 隐私。
在开始项目之前,首要步骤是关注隐私方面的问题。当我们谈论隐私时,它可以是个人数据,如年龄、性别等,也可以是其他机密信息,如政治和宗教偏好。还可能有间接信息,例如车牌号,可以与个人相关联。这些数据必须在整个项目过程中以及之后得到保护和保障。
然而,这并不意味着我们不能使用这种数据。我们可以在项目中使用此类信息,但需要仔细审查其影响,并确保它只能用于官方验证的任务。
使用个人信息需要仔细审查其影响,并确保它只能用于验证的任务。
我们应包括以下几点:
-
一般数据保护条例(GDPR)是否适用?
-
是否有官员参与,例如数据保护官、隐私顾问、信息安全官和首席信息官?
-
你如何考虑输入、模型和结果输出中的潜在不希望的偏见?偏见可能对对象、人物或群体产生假设。误解可能导致不公正的结果,应竭尽全力加以防止。
还有一些变体库可以帮助保护隐私,例如 Faker [2],它可以将真实的名字、地址和其他元信息更改为虚假的。
2. 治理。
如果谈论数据治理,我们需要知道谁是联络人、维护者或负责此数据集的人。这很重要,因为这个人可能会告诉我们更多关于数据集的所有权、可用性、质量、完整性和安全性的信息。除了数据治理之外,还有类似的模型治理,也需要解决类似的问题。那么,谁是联络人、维护者或负责已开发模型的人?因为我们还需要确保模型的质量、完整性和安全性。考虑这一点;我们可以创建一个符合所有规则并且表现出色的模型,但如果有人使用“错误”的输入数据,那么输出将不可信。例如,当数据集中意外缺少值时,或当特征的排序被更改时,也会出现这种情况。许多场景可能导致不可靠的结果。
结论是,需要制定流程来测试输入数据集和模型(输出)的合规性。不同的组织会有不同的流程,只需确保在 AI 系统实施之前已确定这些流程。
3. 数据输入质量。
输入数据集的良好质量对于创建可靠模型至关重要。这听起来可能很简单,但在数据科学项目中需要比现在更多的关注。在最佳情况下,数据集应该没有偏差、没有不准确、错误和其他类型的错误。在实际操作中,我们需要处理可用的数据集,并以最佳方式应对所有挑战。这意味着我们需要负责,并通过进行合理性检查、绘制图表并与数据所有者或领域专家讨论结果来仔细分析数据集的质量(参见治理)。
数据集的质量可以逐步检查,从向数据所有者或领域专家提出广泛的问题开始,然后进行更深入的技术分析。
-
输入(数据)是如何收集和结合的?
-
数据收集是否随时间发生了变化?考虑(新)传感器、环境变化、温度或不同的存储方式等
-
数据是如何标记的?
-
什么因素影响数据的质量?
技术性问题例如以下这些,但它们可能因用例和数据集的不同而有所不同:
-
缺失值的分布
-
检查样本重复
-
检查分类特征和连续特征
-
单变量分析(每个特征)
-
多变量分析以检查可能的相关性
在预检后,我们需要决定在样本数量、特征的可用性和质量的情况下,任务是否可能。请注意,这个质量步骤是在预处理步骤之前的,因为当我们开始预处理时,我们信任数据集并计划将其调整到最佳状态。可以在预检步骤中帮助的一个库是 HNET [4]。它将显示变量之间的关系,并让你探索任何多重共线性。更多细节可以在这里找到:
探索以理解你的数据可以决定一个项目是失败还是成功完成!
[towardsdatascience.com
4. 输出质量。
确保输出的可靠性是另一个需要我们关注的重要任务。输出的类型取决于使用场景,例如可以是仪表盘、建议报告或演示文稿。值得注意的是,我们的输入数据集可能是高质量的,我们的模型也可能经过可靠的训练,但输出仍然可能不可预测甚至不可用。
让我通过一个例子来解释这一点。假设我们创建了一个处理卫星图像并进行物体检测的模型。准确性在训练/测试和验证集上可能很好,但在部署后,输出可能会随着时间的推移而下降,甚至导致差的或不可用的预测。原因是冬季时,云层可能会遮挡视线,从而导致预测效果差或无法预测。
问题不仅是“模型是否有效”,还包括“模型何时有效”。
在我的例子中,建议是只在四月到十月期间使用经过训练的模型,以保持最可靠的预测结果。剩余的时间呢?你需要考虑其他方案。重要的是定期监控模型输出的正确性。
5. 模型质量。
模型质量可以通过其准确性、可靠性、可重复性和可解释性来解决。让我们逐一了解它们。
准确性
术语“准确性”经常与“性能”交织在一起,后者用来量化模型结果。然而,谈到准确性时,我不仅仅是指一个技术指标的分数。诸如 F1 得分这样的指标使用起来都不错,但它们只有在我们为数据和模型设定验收标准时才具有意义。如果没有验收标准,任何分数或任何模型都可能被视为有效,这样做没有意义。
对于每个新项目,我们需要首先确定验收标准。
值得注意的是,验收标准不仅仅是一个期望的分数。它还可以描述方法。例如,可能期望得到可解释的结果,其中输出需要是二元的。或者期望最佳表现的模型是三种测试模型中表现最好的。或者可能期望拥有最低的 F1 分数,因为只有这样模型才能有用,而不是传统的方法。
重要的是,验收标准要与数据和预期用途相匹配。
可靠性与可重复性
可靠性和可重复性是密切相关的,因为一个可靠的模型是在类似情况下能产生一致结果的模型。
要使模型变得可靠,我们需要确切知道训练过程中使用了哪些样本和特征,以及是否有部分数据被排除在学习过程之外。如果有,那么我们需要将解释和结论限制在那个特定的样本和/或特征集上。此外,为了构建一个可靠的模型,我们需要将训练/测试和验证集分开,以防数据泄漏。如果使用了(超)参数调优,可能甚至需要使用双重循环交叉验证。HGboost 库使用这种干预措施来防止数据泄漏并意外发现过拟合模型。更多细节请见:
## 使用贝叶斯超参数调优寻找最佳提升模型的指南,但不包括…
提升决策树算法可能优于其他模型,但过拟合是一个实际的危险。使用 HGBoost 库来拟合你的模型。
towardsdatascience.com
我们可以通过基于输入数据、给定的超参数和固定种子重建模型来检查项目是否具备可重复性。此外,我们还可以检查输出结果是否可以被重现。
可解释性
可解释性是指模型和结果是否足够清晰和易于开发人员解释。此外,设计选择必须清晰且逻辑上符合底层模型。除了技术部分,还期望有足够的文档来解释模型的操作方式。
6. 伦理。
道德应贯穿整个项目。关键是意识和诚信。此外,与相关方和用户保持透明,说明模型的局限性,并在开发和审核模型时运用常识总是很重要的。
负责任并不会占用你更多时间!
启动一个高责任感的项目可能需要(在开始时)一些额外的组织,因为你可能需要设立一些检查点并讨论每个人的角色。然而,如果所有检查点到位,它可以很容易地成为项目流程的一部分,而不会比常规项目多占用很多额外时间。此外,我预计(更有经验的)数据科学家已经处理了一些AI要点,这些要点在六个部分中有所描述。许多描述的部分,例如输入、模型和输出质量,并不是“新”的,只是可能需要一些额外的关注。
结束语。
在许多领域中,利用 AI 优化流程的机会有很多。有些会比其他的更有影响力,但使用 AI 时的基本原则应该是负责任。确保对于影响重大的项目,决策模型是透明和可解释的,人手需要能够进行事实核查、修正和调整。
保持安全,保持警觉。
敬上 E
如果你觉得这篇文章有帮助,欢迎 关注我 ,因为我会写更多关于数据科学的话题。如果你考虑购买 Medium 会员,可以通过使用我的 推荐链接来支持我的工作。这与一杯咖啡的价格相同,但允许你每月阅读无限的文章!
让我们联系吧!
参考资料
-
towardsdatascience.com/the-path-to-success-in-data-science-is-about-your-ability-to-learn-but-what-to-learn-92efe11e34bf
-
E. Taskesen, 通过贝叶斯超参数调整找到最佳增强模型的指南, Medium 2022 年 8 月
-
E. Taskesen, 探索并理解你的数据与一个重要关联网络。 Medium 2021 年 8 月
-
Dan Hendrycks 等,灾难性 AI 风险概述, ArXiv, 2023 年。
恶名昭著的 XGBoost
回顾最受奖项青睐的机器学习算法之一
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 5 月 20 日
–
XGBoost 的化身 — 作者 + OpenJourney
如果你是一名从事监督学习问题的数据科学家,你可能已经注意到 XGBoost 经常在你的排行榜上名列前茅。它的成功很大程度上归功于该算法在捕捉难以捉摸的非线性关系方面的卓越准确性。尽管现在还为时已晚,尚无法判断 XGBoost 是否会在各种最先进的通用模型涌现后幸存,但 XGBoost 的一贯可靠性促使我们回顾其背后的优雅数学。
XGBoost(极端梯度提升)是一种强大的基于树的集成技术,特别擅长完成分类和回归任务。它基于梯度提升机(GBM)算法,通过结合多个弱模型(在这种情况下是决策树)来形成更强的模型(Friedman, 2001)。XGBoost 的学习过程可以分解如下:
分解
目标函数
XGBoost 的基本原理是最小化目标函数。目标函数,表示为 Objective(T),结合了训练损失(对训练数据的评估)和正则化项(防止过拟合)。
Objective(T) = Loss + Regularization
XGBoost 的目标函数如下所示:
Objective(T) = ∑l(yi, y_pred,i) + ∑Ω(f)
其中:
-
T 代表决策树的集成
-
l(y, y_pred) 是一个可微分的凸损失函数,衡量真实输出 (y) 和预测输出 (y_pred) 之间的差异
-
yi 是真实输出,例如 i
-
y_pred,i 是实例 i 的预测输出
-
Ω(f) 是对集成中的每棵树 (f) 应用的正则化项 (T)
加法训练
XGBoost 以加法方式学习目标函数,创建一个迭代的决策树集成(弱学习器),逐步最小化目标函数。在每次迭代中,向集成中添加一棵新树,并优化目标函数。
为了形式化这一点,我们考虑以下内容:
Fm(x) = Fm-1(x) + fm(x)
其中:
-
Fm(x) 是添加了 m 棵树后的预测
-
Fm-1(x) 是预测值直到 m-1 棵树
-
fm(x) 是在 m-th 迭代中添加的新树
梯度和 Hessian 计算
为了最小化目标函数,XGBoost 使用梯度下降。在每次迭代中,计算损失函数相对于预测输出 (y_pred) 的一阶和二阶导数(梯度和 Hessian)。
Gradient(g): ∇l(y, y_pred) = d(l) / dy_pred
Hessian(h): ∇²l(y, y_pred) = d²(l) / dy_pred²
对于数据中的每个实例 (i),计算导数,得到梯度和 Hessian 值的向量 g 和 h。
树的构造
在 m-th 迭代中,使用计算得到的梯度和 Hessian,添加最小化目标函数的最佳树 (fm)。XGBoost 从空树开始,然后依次分裂每个叶子以最小化以下方程:
Gain = 1/2 * [Gl² / (Hl + λ) + Gr² / (Hr + λ) - G² / (H + λ)] - γ
其中,
-
Gl 和 Gr 是分裂后左侧和右侧区域的梯度之和
-
Hl 和 Hr 是分裂后左侧和右侧区域的 Hessian 之和
-
G = Gl + Gr,整个节点的梯度之和
-
H = Hl + Hr,整个节点的 Hessian 矩阵之和
-
λ,L2 正则化项
-
γ,进行分裂所需的最小损失减少(另一个正则化项)
增益方程结合了损失减少和正则化项,这有助于防止过拟合并在复杂度和预测能力之间做出最佳权衡。
超参数
learning_rate: (eta) 控制每次迭代时的更新步长,缩小每棵树的效果以防止过拟合。它是新添加到集成中的树的权重因子。
max_depth: 树的最大允许深度。随着深度的增加,模型变得更加复杂,可能会过拟合数据。
min_child_weight: (最小实例权重和 H) 进行树的分裂所需的最小 Hessian 值和。增加此值可以通过使树更为保守来防止过拟合。
lambda: 权重的 L2 正则化项,作为增益计算的一部分应用。帮助控制模型复杂性。
gamma: (min_split_loss) 进行进一步分裂所需的最小损失减少。控制树的生长和复杂性。
subsample: 每次提升轮次中从训练集中采样的比例。随机选择数据子集通过引入随机性来减少过拟合的可能性,从而提高集成过程的鲁棒性。
colsample_bytree: 每次提升轮次中选择的特征比例。随机选择列(特征)来构建相关性较小的树,防止过拟合。
实际上,这些超参数影响树的构建和增益计算(如 λ 和 γ),或者每次迭代中选择数据和特征的过程(如 subsample 和 colsample_bytree)。调整这些超参数有助于平衡模型复杂度和预测能力,提高性能,同时减轻过拟合。
简而言之,XGBoost 通过迭代地构建决策树的集成来学习,最小化由训练损失和正则化项组成的目标函数。它利用梯度下降来找到最佳树,采用损失函数的一阶和二阶导数。XGBoost 利用超参数,如最大深度、正则化参数和特征及实例的子采样策略,以防止过拟合并提高计算效率。值得注意的是,特别是子采样,通过在每次迭代中处理较少的数据点,引入了随机性和多样性,从而减少过拟合的机会并加快训练过程。
独特特征
陈天奇和古斯特林强调了几个使 XGBoost 与其他提升算法不同的独特特征,并提升了其性能(陈天奇和古斯特林,2016)。这些特征包括:
稀疏感知
XGBoost 旨在有效处理稀疏数据,这在包含缺失值或零值的现实世界数据集中很常见。XGBoost 使用一种考虑稀疏性的算法来为缺失值的数据点找到最佳分裂,从而提高了对稀疏数据的性能。
具体来说,XGBoost 在树构建过程中采用了默认(缺失值)方向。当特征值缺失时,算法会自动选择产生最高增益的方向,而不会对缺失值进行显式分裂。这种考虑稀疏性的方式使 XGBoost 高效,并减少了树构建所需的信息量。
正则化提升:
如前所述,XGBoost 在树构建过程中引入了正则化项(L1 和 L2),这有助于控制模型的复杂性并减少过拟合。这与缺乏正则化组件的传统 GBM 有一个关键区别。
列块(缓存感知)和并行学习:
XGBoost 支持树构建过程中的并行处理,使其能够利用多个处理器核心以加快学习速度。该算法按列排序数据,并以压缩形式存储在列块中。XGBoost 通过使用缓存感知算法预取列块,确保树构建过程中的高效内存访问,使其适合处理大数据集。
假设、优势和缺点
假设
简而言之,XGBoost 假设弱学习器(决策树)可以组合成一个更强大、更稳健的模型。它还假设目标函数是连续的、可微的和凸的。
优势
高性能:XGBoost 在分类和回归任务中始终取得最先进的结果。
可扩展性:XGBoost 高效利用内存和计算资源,使其适合大规模问题。
正则化:内置正则化项有助于防止过拟合。
稀疏意识:XGBoost 被设计为有效处理稀疏数据。
并行性:支持并行和分布式计算以加快学习速度。
缺点
计算复杂性:尽管 XGBoost 高效,但对于大数据集或大规模集成方法,它仍可能计算开销较大。
可解释性:尽管线性回归或单一决策树等参数化方法具有固有的可解释性,但像 XGBoost 这样的非参数集成方法需要额外的解释方法,如 Tree SHAP,以解释结果。
对超参数的敏感性:XGBoost 的性能受超参数的影响,通常需要微调以获得最佳结果。
对社会偏见、可信度和安全性的敏感性
像其他机器学习算法一样,XGBoost 的性能高度依赖于输入数据的质量。虽然算法本身可能不会表现出与算法偏见、可信度或安全漏洞相关的弱点,但这些问题可能由于偏见采样、不当应用或不公平解释模型结果而出现。
社会偏见
XGBoost 可能会无意中传播或甚至放大数据中存在的社会偏见。当训练数据反映出代表性不足、歧视或延续的刻板印象时,XGBoost 模型将不可避免地学习这些模式,可能导致有害的结果。确保数据中的代表性并解决社会偏见对于减少相关风险至关重要。
可信度
XGBoost 是一个决策树的集成,可能会导致复杂的模型,难以解释。这种缺乏透明度可能使利益相关者难以信任模型并理解其决策过程。像 Shapley 加法解释(SHAP)这样的方法已帮助减少“黑箱”问题,但解释性仍然是一个关注点(Lundberg 和 Lee 2017;Rudin 2019)。
安全性
机器学习模型,包括 XGBoost,可能会受到对抗性攻击、数据中毒或逆向工程的影响,这些可能暴露敏感信息(即去匿名化)或影响模型性能。确保数据集的安全性和保护模型免受恶意攻击对于维护系统的完整性和稳健性至关重要。此外,篡改或改变输入数据的来源可能导致误导性或不正确的预测,这引发了关于模型可信度的问题。
结论
XGBoost 是一个强大且多功能的机器学习算法,由于其卓越的性能、可扩展性和效率,它在排行榜上占据了主导地位。通过利用集成学习、梯度下降和正则化技术,XGBoost 克服了许多传统提升方法的局限,同时适应处理稀疏数据并优化计算资源。
然而,必须承认,任何机器学习模型,包括 XGBoost,相关的潜在风险依赖于算法的负责任使用。具体而言,通过仔细的数据预处理、通过解释性技术增强透明度和实施稳健的安全措施,可以帮助应对这些挑战,并确保 XGBoost 模型在实践和伦理上都符合要求。
采纳伦理原则和最佳实践可以让我们继续利用 XGBoost 和其他机器学习技术的力量,同时推动这些技术在未来带来公平和有益的成果。
参考文献
1. Chen T, Guestrin C. XGBoost:一种可扩展的树提升系统。发表于:第 22 届 ACM SIGKDD 国际知识发现与数据挖掘会议(KDD ‘16);2016 年 8 月 13–17 日;加州旧金山。纽约:计算机协会;2016 年。第 785–794 页。DOI: doi.org/10.1145/2939672.2939785
。
2. Friedman JH. 贪婪的函数近似:一种梯度提升机。统计年鉴。2001 年;29(5):1189–1232。
3. Lundberg SM, Lee SI. 统一的模型预测解释方法。发表于:神经信息处理系统进展(NIPS 2017);2017 年 12 月 4–9 日;加州长滩。2017 年。
4. Rudin C. 请停止解释用于高风险决策的黑箱模型。arXiv:1811.10154。2019 年。
人工智能奥运会:机器学习系统的基准比赛
基准如何催生突破?
·
关注 发表在 走向数据科学 ·13 分钟阅读·2023 年 9 月 22 日
–
你无法改善你不测量的东西。 — 彼得·德鲁克
奥运五环。图片由作者创建。
《四分钟英里:重新定义跑步的基准》
多年来,跑完一英里在四分钟内被认为不仅是一个艰巨的挑战,而且被许多人视为一个不可能的壮举。这是一个心理和身体的基准,许多人认为是无法达到的。医生和体育专家理论认为,人体不可能以如此快速的速度跑那么长时间。这种信念根深蒂固,甚至有些人认为尝试这样做可能是致命的。
罗杰·班尼斯特爵士,一位英国中长跑运动员和医学生,持不同观点。他虽然认识到挑战,但认为障碍更多的是心理上的而非生理上的。班尼斯特采取了科学的方法进行训练,将一英里分成几个部分,并严格计时。他还采用了基于间歇训练的严格训练计划,并在尝试创纪录之前为自己设定了较小的基准。
1954 年 5 月 6 日,在英格兰牛津的一条跑道上,在朋友克里斯·布拉舍尔和克里斯·查塔维作为领跑者的帮助下,班尼斯特尝试突破四分钟障碍。他以 3 分钟 59.4 秒完成了一英里,打破了这一门槛,创造了历史。
罗杰·班尼斯特在比赛中奔跑。图片来源:挪威百科全书 (CC-BY 4.0)。
班尼斯特成就的后果是高度意外的。贡德·海格的 1945 年纪录(4 分钟 1.4 秒)保持了近十年才被班尼斯特打破。然而,一旦四分钟英里基准被突破,其他人很快跟随其后。在班尼斯特跑步后的 46 天,约翰·兰迪完成了一英里,时间为 3 分钟 57.9 秒。在接下来的十年里,这一纪录又被打破了 5 次。目前的纪录由希沙姆·埃尔·盖鲁吉创造,时间为 3 分钟 43.1 秒。
1900 年至 2000 年期间的世界纪录英里时间。注意 1945 年到 1954 年之间的间隔,直到罗杰·班尼斯特打破四分钟英里基准——否则,下降趋势几乎是线性的。图由作者创建。
班尼斯特的成就展示了基准的力量,不仅作为性能的衡量标准,也作为变革的激励。一旦四分钟的“基准”被打破,它重新定义了运动员认为可能的极限。这个障碍既存在于思想中,也存在于跑道上。
四分钟英里体现了基准在各学科中的变革力量。基准为特定任务的性能改进提供了量化的方法,让我们可以与他人进行比较。这是奥运会等体育赛事的全部基础。然而,基准只有在参与的社区能够确定共同目标时才有用。
在机器学习和计算机科学领域,基准测试是社区的奥林匹克——一个宏大的竞技场,在这里,算法、系统和方法论竞争的不是奖牌,而是进步的自豪感和创新的动力。正如运动员为了奥林匹克金牌而训练多年、争取毫秒级的进步一样,开发者和研究人员优化他们的模型和系统,以提高性能,力求在既定基准测试中超越对手。
基准测试的艺术与科学在于建立那个共同目标。这不仅仅是设定一个任务,而是确保它能够捕捉到现实世界挑战的本质,推动可能性的边界,同时保持相关性和适用性。选择不当的基准测试可能会使研究人员走入歧途,优化的任务无法在现实世界应用中带来改进。一个设计良好的基准测试可以引导整个社区向突破性进展迈进,重新定义一个领域。
因此,虽然基准测试是用于比较和竞争的工具,其真正的价值在于它们能够团结一个共同愿景的社区。就像班尼斯特的跑步不仅打破了纪录,还重新定义了运动潜力一样,一个构思良好的基准测试可以提升整个学科,改变范式并引领创新新时代。
在这篇文章中,我们将探讨基准测试在推进计算机科学和机器学习中的关键作用,通过回顾其历史,讨论基准测试机器学习系统的最新趋势,了解它如何在硬件领域推动创新。
基准测试计算系统:SPEC
在 1980 年代,随着个人计算机革命的兴起,对标准化指标以比较不同计算机系统性能的需求日益增长:一个 基准测试。在标准化基准测试出现之前,制造商通常会开发并使用他们自己定制的基准测试。这些基准测试往往突出了他们机器的优点,同时淡化了其缺点。显然,需要一个中立且被广泛接受的基准测试来进行比较。
为了解决这一挑战,系统性能评估合作组织(SPEC)应运而生。该组织的成员包括硬件供应商、研究人员以及其他致力于创建通用基准标准的利益相关者,主要用于对中央处理单元(CPUs),也称为‘芯片’进行基准测试。
SPEC 的首个重大贡献是SPEC89基准测试套件,它在行业标准 CPU 基准测试中开创了先河。SPEC 的基准测试聚焦于实际应用和计算任务,旨在提供对最终用户有意义的指标,而不是那些深奥或小众的测量。
然而,随着基准测试的发展,出现了一个有趣的现象:所谓的“基准效应”。随着 SPEC 基准测试成为测量 CPU 性能的黄金标准,CPU 设计师开始针对 SPEC 的基准测试优化他们的设计。实质上,由于行业已经将 SPEC 基准测试视为整体性能的衡量标准,制造商有强烈的动力确保他们的 CPU 在这些测试中表现出色——即使这可能意味着在非 SPEC 任务中牺牲性能。
这并非 SPEC 的初衷,导致了计算机科学社区内部的激烈辩论。这些基准测试是否真正代表了现实世界的性能?还是它们驱动了一种狭隘的视角,使得基准测试成为目的本身而不是实现目标的手段?
认识到这些挑战后,SPEC 多年来不断更新其基准测试,以保持领先并防止过度优化。他们的基准测试套件扩展到涵盖不同领域,从整数和浮点计算到更具体领域的任务,如图形、文件系统等。
SPEC 及其基准测试的故事强调了基准测试对整个行业方向的深远影响。这些基准测试不仅仅是衡量性能——它们还影响了性能。这证明了标准化的力量,但也提醒我们,当一个单一的指标成为优化的焦点时,可能会出现意想不到的后果。
今天,SPEC 基准测试以及其他基准测试继续在塑造计算机硬件行业和指导消费者及企业的购买决策中发挥至关重要的作用。
深度学习基准测试:ImageNet
在 2000 年代末,计算机视觉,一个专注于使机器能够解读和基于视觉数据做出决策的 AI 子领域,正面临进展缓慢的问题。传统技术有所进展,但在许多任务上已经达到性能平台期。当时的方法严重依赖于手工制作的特征,要求专家精心设计和选择每个任务的特定特征。这是一个繁琐且有许多限制的过程。
随后发布了ImageNet,这是由李飞飞博士及其团队发起的一个庞大的视觉数据库。ImageNet 提供了数百万张标记图像,涵盖了数千个类别。这个数据集的庞大规模前所未有,仅通过Amazon Mechanical Turk等云端方法进行众包数据标记才得以实现。ImageNet 是最早的数据集基准之一——自发布以来,ImageNet 的论文已被引用超过 50,000 次。
图像 Net 图像的视觉编译。图像来源:Gluon(CC-BY 4.0)。
但收集数据集只是开始。2010 年,ImageNet 大规模视觉识别挑战(ILSVRC)启动。挑战的目标简单,但规模庞大:将图像自动分类到 1,000 个类别中的一个。这个基准挑战将提供计算机视觉进步的客观衡量,超越了以往的尝试。
初期几年在传统方法上取得了渐进式的改进。然而,2012 年的挑战带来了变革性的转变。由亚历克斯·克里兹赫夫斯基、伊利亚·苏茨克弗和杰弗里·辛顿领导的多伦多大学团队推出了一种深度卷积神经网络(CNN),称为“AlexNet”。他们的模型实现了 15.3%的错误率,将前一年的错误率几乎削减了一半!
ImageNet 大规模视觉识别挑战的错误率。自 2012 年深度学习引入以来,准确率显著提高,并持续改善。人类的错误率约为 5%。图像来源:2018 NIH/RSNA/ACR/The Academy Workshop。此图像依据知识共享署名 4.0 国际许可协议(CC BY 4.0)进行复制。
什么使这一切成为可能?深度学习,特别是卷积神经网络(CNN),能够直接从原始像素中学习特征,省去了手动特征设计的需求。只要有足够的数据和计算能力,这些网络可以揭示出传统方法无法处理的复杂模式。
AlexNet 的成功是人工智能发展中的一个分水岭。2012 年后,深度学习方法主导了 ImageNet 挑战,驱动错误率不断降低。从基准测试中传达的明确信息是不容忽视的:深度学习,曾经是机器学习中的一个小众领域,现在正准备彻底改变计算机视觉。
而且它不仅仅是这样。ILSVRC 中的成功作为催化剂,将深度学习推到了计算机视觉以及从自然语言处理到游戏玩耍等众多 AI 领域的前沿。挑战突显了深度学习的潜力,吸引了研究人员、资金和关注。
通过设定一个明确且具有挑战性的基准,ImageNet 挑战在重新调整 AI 研究轨迹方面发挥了关键作用,促成了我们今天见证的深度学习驱动的 AI 复兴。
机器学习系统的基准测试:MLPerf
像 SPEC 和 ImageNet 这样的基准的变革性影响自然引发了一个问题:接下来是什么?随着深度学习模型变得越来越复杂,它们的计算需求也在增加。这将注意力转向了另一个关键组成部分——支撑这些模型的硬件。进入MLPerf。
MLPerf 作为一个涉及行业巨头和学术机构的合作努力出现,旨在创建一套标准的基准来测量机器学习硬件、软件和云平台的性能。正如名字所示,MLPerf 明确专注于机器学习,涵盖从图像分类到强化学习的广泛任务。目标明确——在一个“最佳性能”声明变得司空见惯但往往基于不一致标准或挑选指标的领域中提供清晰度。
MLPerf 的引入为科技行业提供了一个迫切需要的统一标准。对于学术界,它提供了明确的性能目标,促成了一个可以轻松测量和比较算法创新的环境。对于行业,尤其是硬件制造商,它既是挑战也是机会。新芯片不能再以模糊的机器学习性能声明推出——现在有了一个被普遍接受的基准,任何此类声明都将接受考验。
就像 SPEC 影响了 CPU 设计一样,MLPerf 开始塑造 AI 硬件的方向。公司们开始根据 MLPerf 基准优化他们的设计,这不仅仅关乎原始性能。基准测试还融入了效率指标,鼓励那些不仅提供速度而且具备能源效率的创新——在巨大的变换模型和环保意识的时代,这是一个迫切关注的问题。这些基准测试被像 Nvidia 和 AMD 这样的科技公司日常使用,以展示他们的新硬件。
Nvidia H100 在 MLPerf Inference v3.0 数据中心的标准化性能对比之前的 Nvidia A100 系统。如图所示,与前一代芯片相比,H100 在全尺寸大型语言模型 BERT 上的速度提高了 4 倍。图片来源:MLCommons和Nvidia Blogs。图片经 MLCommons 许可转载。
如今,有数十种类似 MLPerf 的基准测试,由MLCommons管理,包括:
-
MLPerf Training. 用于在训练机器学习模型时评估系统性能(与研究人员相关)。
-
MLPerf Inference. 用于在执行机器学习模型推理时评估系统性能(与通过云托管模型的公司相关)。MLPerf Inference 有多个版本,关注数据中心、移动设备、边缘设备和微型机器学习设备。
-
MLPerf Training HPC. 用于基准测试与高性能计算系统相关的工作负载。
-
MLPerf Storage. 用于基准测试与存储系统相关的工作负载。
但 MLPerf 并非没有批评者。正如任何获得显著关注的基准测试一样,存在对“过拟合”基准测试的担忧,即设计过度优化基准测试,可能以牺牲实际应用性为代价。此外,还面临着确保基准测试保持相关性的挑战,及时更新以反映机器学习领域的快速进展。
尽管如此,MLPerf 的故事,与其前辈一样,强调了一个基本真理:基准测试催化进步。它们不仅仅衡量最先进的技术;它们塑造它。通过设定明确而具有挑战性的目标,它们集中集体的精力,推动行业和研究社区开拓新领域。在一个人工智能不断重新定义可能性的世界里,拥有一个指引其复杂性的指南针不仅仅是可取的,而是必不可少的。
生成性 AI 的基准测试挑战
除了 AI 硬件,大型语言模型,一种生成性 AI,是基准测试工作的关键重点。更普遍地称为基础模型,这些比硬件或其他类型的机器学习模型更难以基准测试。
这是因为语言模型的成功不仅仅依赖于原始计算速度或在狭义任务中的准确性。相反,它取决于模型在各种提示和上下文中生成连贯、具有上下文相关性和信息性的回应的能力。此外,评估回应的“质量”本质上是主观的,并可能根据应用程序或评估者的偏见而有所不同。鉴于复杂性,像GPT-3或BERT这样的语言模型的基准必须比传统基准更加多样和多方面。
最著名的语言模型基准之一是通用语言理解评估(GLUE),该基准于 2018 年开发。GLUE 不仅仅是一个单一任务;它是一个包含九种多样化语言任务的集合,从情感分析到文本蕴含。其目的是提供全面的评估,确保模型不仅仅在一个任务上表现出色,而是真正能够在各种挑战中理解语言。
GLUE 的影响立竿见影。首次出现了一个明确、一致的基准,用于评估语言模型。不久之后,科技巨头和学术界也纷纷参与,每个参与者争夺 GLUE 排行榜的首位。
当GPT-2首次在 GLUE 基准上进行评估时,它取得了当时令人惊叹的成绩,超过了许多模型。这不仅证明了 GPT-2 的强大能力,还强调了 GLUE 在提供明确测量标准方面的价值。能够声称“在 GLUE 上达到最先进水平”成为了社区中备受追捧的认可。
然而,GLUE 的成功是把双刃剑。到 2019 年底,许多模型开始在 GLUE 的排行榜上达到饱和,分数接近人类基线。这种饱和突出了基准测试的另一个关键方面:基准需要随着领域的发展而进化。为了解决这个问题,同一团队推出了SuperGLUE,这是一个更为严格的基准,旨在进一步推动边界。
像GLUE、SuperGLUE和SQuAD这样的基准被用来评估模型在情感分析和问答等特定任务上的表现。但这些基准只是触及了基础模型旨在实现的目标的表面。除了任务特定的准确性,出现了其他维度来评估这些模型:
-
鲁棒性。 模型如何处理边缘情况或对抗性输入?鲁棒性基准测试通过设计旨在困惑或误导模型的输入来挑战模型,评估它们对恶意行为者或意外情况的抵御能力。
-
泛化能力和迁移学习。 基础模型应能在未被明确训练的任务上表现良好。评估模型的零样本或少样本学习能力,即在几乎没有先前示例的情况下完成任务,对于了解其灵活性和适应性至关重要。
-
交互性和连贯性。 对于聊天机器人或虚拟助手等应用,模型在长时间互动中的一致性和连贯性至关重要。该领域的基准测试可能涉及长对话或在多个交流中保持上下文。
-
安全性和可控性。 随着模型规模的增加,这些基准测试确保模型不会产生有害、不适当或无意义的输出是至关重要的。
-
可定制性。 随着基础模型的广泛应用,对其进行特定领域或应用的定制需求日益增加。该领域的基准测试可能会评估模型在新数据集上的微调能力或适应特定行业术语和细微差别的效果。
一个有趣的发展是,随着语言模型的表现越来越接近人类表现,历史上用于评估人类表现的测试现在也被用作语言模型的基准测试。例如,GPT-4 在 SAT、LSAT 和医学考试等考试中进行了测试。在 SAT 中,它得分 1410,排名全国前 6%。GPT-4 甚至能够通过所有版本的医学考试,平均得分为 80.7%。然而,在 LSAT 中,它的得分较低,分别为 148 和 157,位于第 37 和第 70 百分位。
GPT 在学术和专业考试中的表现。图源自“GPT-4 技术报告” 。图像来源:OpenAI (CC-BY 4.0)。
观察基准测试方法如何继续发展以应对语言模型在许多领域逐渐与人类表现相媲美甚至超越人类,将会很有趣。
基准测试的未来
基准测试的未来正在快速发展,日益多样化,以应对新兴技术和应用的广泛范围。以下是一些正在实施基准测试的新兴领域示例:
-
RobotPerf:随着机器人技术在我们日常生活中越来越多地集成,像 RobotPerf 这样的基准测试正在被设计用来专门衡量和加速机器人应用,确保机器在效率和安全标准上达到要求。
-
NeuroBench:在脑启发计算领域,NeuroBench 在评估类脑系统方面开创了先河,提供了这些架构在多大程度上模拟神经过程的见解。
-
XRBench:虚拟现实和增强现实领域随着 Meta 和 Apple 进入这一领域并推出新硬件而复苏。为此,XRBench 被开发以专注于扩展现实(XR)应用,这对于提供沉浸式和无缝的用户体验至关重要。
-
MAVBench:随着无人机在多智能体系统和电池技术进步下变得越来越具有商业相关性,像 MAVBench 这样的基准测试将在优化这些系统的性能方面发挥重要作用。
计算机科学和机器学习社区对基准测试在推动领域进步中的重要性深有认识。现在,连 NeurIPS,作为旗舰 AI 会议之一,也专门设置了一个 track,专注于数据集和基准测试。现在已进入第三年,这个 track 正在获得巨大的动力,今年单是提交的数量就接近 1,000 个。这一趋势突显出,随着技术不断前进,基准测试将继续在实时中指导和塑造其轨迹,正如过去所做的那样。
结语
基准测试在塑造进步方面的作用,无论是在体育还是人工智能领域,都不可低估。它们既是镜子,反映当前的状况,也是窗口,提供未来潜力的瞥见。随着人工智能继续影响从医疗保健到金融等各种应用和行业,拥有强大的基准测试变得至关重要。它们确保进展不仅迅速而且有意义,将努力引向真正重要的挑战。正如 Sir Roger Bannister 通过他的四分钟一英里向我们展示的那样,有时最令人畏惧的基准,一旦被征服,能够在未来几年释放出创新和灵感的波澜。在机器学习和计算的世界里,这场竞赛远未结束。
一页数据和分析模板
原文:
towardsdatascience.com/the-one-page-data-and-analytics-templates-f53b949be84
使用五个模板掌握数据和分析报告及流程
·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 3 月 2 日
–
图片由 micheile dot com 提供,来源于 Unsplash
介绍
在我职业生涯早期得到的最佳建议是为任何报告和开发流程创建模板。
尽管最初我发现创建模板耗时,但这一步骤在开发和规划前阶段为后续工作节省了大量时间。
这是我现在努力传授给所有同事和团队成员的知识。
我对模板化数据和分析报告及流程的原因主要有三个:
#1: 可重用性 — 模板可以被重用并适应各种数据项目和流程。
-
作为数据负责人,您可以通过创建用于重复报告和可扩展流程的模板来节省时间。这可以包括各种模板——从利益相关者关于团队开发进展的报告到内部团队的会议记录和开发/测试流程。
-
作为数据专业人员,一旦您已经有了用于总结开发方法和分析见解的预定义模板,您在创建开发者/终端用户文档时可以变得更加高效。
#2: 准确性 — 模板可以确保数据报告和流程包含组织和数据团队所需的所有相关信息。
- 通过对团队开发更新进行模板化,您可以减少报告错误,并确保跨组织和团队间对所有必要信息的轻松跟踪。
#3: 一致性 — 模板可以确保数据流程和见解的一致性和清晰度。
- 通过预定义的数据模板,您的利益相关者将习惯现有的文档结构,并能快速找到所需的信息,即使是在新记录的流程和见解中。
基于这三个关键点,说明为什么需要模板化数据和分析流程及报告,我整理了一套可供你在数据团队和组织中使用的模板。
模板
以下列出的数据和分析模板用于团队组织和分享开发更新。对于每个模板,我将列出背景故事(开发原因)和使用范围。
#1: 数据和 分析 路线图模板:努力-价值矩阵
数据和分析路线图模板 [作者图片]
背景故事:
该模板在数据和分析团队刚刚成立时创建。其目的是分享和维护清晰的数据和分析愿景,与组织成为数据驱动型公司的愿景保持一致。
因此,创建了数据和分析路线图,将不同领域(数据工程、数据分析和数据科学)的计划用例映射到努力-价值矩阵中(艾森豪威尔矩阵)。
提供的模板有开发版本,即随着数据用例优先级的变化,模板也随之演变。
使用范围:
为数据和分析路线图开发的模板作为数据和分析团队的开发优先级或“快速胜利”的指引。
该模板还用于数据和分析团队的结构化和计划性增长,因为我们知道下一阶段的路线图交付需要多少人力资源。
此外,该模板帮助业务负责人轻松跟踪基于优先业务用例的分析开发阶段。
#2: 数据和 分析 复盘会议模板:每周跨团队报告
数据和分析复盘会议记录模板 [作者图片]
背景故事:
数据和分析每周复盘团队会议模板的创建旨在标准化跨组织和团队的公告及报告,涉及团队的Sprint任务。
除了改善团队间的沟通外,该模板旨在确保特定数据领域的信息和工作透明。
使用范围:
除了简化沟通外,该模板用于数据负责人跟踪团队进度并识别需要改进的领域。通过结构化的报告方式,可以轻松比较不同周的记录,并识别出随时间变化的趋势。
对于团队成员,该模板鼓励对自己的工作进行反思。此外,它提供了必要的信息和知识交流,关于同事们完成的工作(任务的定量和定性描述)。
#3: 数据和 分析 开发文档模板:用于解释开发和最终用户见解的文档模板
数据和分析开发及最终用户文档模板 [作者提供的图像]
背景故事:
创建开发和最终用户文档模板的目标是提高团队的效率(减少文档结构设计的时间),并通过以结构化的方式解释数据见解来为最终用户提供更多背景信息。
使用范围:
数据分析师/科学家使用开发文档模板以结构化的方式解释数据建模过程,即输入 → 输出 流程。
最终用户文档模板供利益相关者快速查找新创建的数据见解(仪表板和报告)的解释。因此,最终用户文档被结构化为详细说明主要仪表板元素:基础数据模型、时间范围方法、分析总结,以及仪表板回答的业务问题。
#4: 数据和 分析 状态更新模板:每月利益相关者报告
数据和分析月度状态更新模板 [作者提供的图像]
背景故事:
数据和分析月度状态更新模板的创建旨在向公司所有利益相关者总结不同业务领域(业务控制、物流、绩效营销)的开发更新。
使用范围:
数据负责人使用该模板以简单而结构化的方式向首席执行官和组织领导汇报数据和分析团队的成就。
此外,利益相关者使用该模板来规划新数据开发的业务行动。
#5: 数据和 分析 年终报告模板:开发亮点
数据和分析年终报告模板 [作者提供的图像]
背景故事:
年终数据和分析报告模板的创建旨在突出年度团队的成长和发展。
使用范围:
数据负责人使用该模板以简单而结构化的方式向首席执行官和组织领导汇报数据和分析团队的成就。
此外,这一页的报告模板可以用于制定来年的开发计划并展示未来的数据项目。
结论
本博客文章旨在展示五个一页式的数据和分析模板,这些模板可以被任何组织中的数据团队/数据负责人或数据利益相关者使用,以提高报告质量、信息共享和团队效率。
作为数据负责人,我发现一页式模板在我需要进行工作质量检查、提高团队协作以及使利益相关者更容易阅读报告并节省寻找所需信息的时间时,具有很大好处。
因此,我想与你分享这些模板。
我希望你觉得它们易于使用,并且能够帮助你按计划进行报告和团队管理。
如果你想了解如何组织和模板化你的完整知识库,请查看这里的帖子:
以系统化的方式创建外部、共享和内部文档
towardsdatascience.com
学习递归所需了解的唯一一件事
(以及 更多 )
·
关注 发布在 Towards Data Science ·9 分钟阅读·2023 年 4 月 14 日
–
照片由 Szabo Viktor 提供,发布在 Unsplash 上
有一句老生常谈的笑话:
要理解递归,你需要理解递归。
这是一个只有程序员、数学家或逻辑学家会觉得有趣的笑话。它也不是真的。
像许多其他事物一样,理解递归的唯一方法是练习。编写大量简单的递归函数。然后再写更多。再接着写一些更复杂的递归函数。依此类推。但你怎么做呢?我有一个小窍门。这是一本书。它很短(不到 200 页),引人入胜,并且会教会你比写递归更多的东西。它还会向你介绍计算机科学中一些最基础的概念,例如 Y 组合子、解释器、组合子和停机问题。
这本奇妙的小书叫做*《小程序员》*。¹ 在“前言”中,几乎在一开始,作者明确指出:
这本书的目标是教会读者递归思维。
如果你阅读了*《小程序员》*,递归将成为你思考问题的一种熟悉方式。但是,在此过程中,你会学到大量的基本理念。此外,这本书采用了非常独特的风格,迫使你解决其中的问题并吸收其概念。
接下来,我将简要概述什么是递归以及为什么你应该学习它。然后我将介绍这本书,并解释为什么它是一种出色的工具,可以让你成为更好的程序员。
注意:如果你是完全的初学者,并且对更抽象的主题不感兴趣,这本书可能不适合你。如果你处于想要学习编程并且希望有所展示的阶段,我的建议是等到你对某一种编程语言感到舒适时再回过头来看这本书。再者,如果你对编程的兴趣更偏向于哲学或理论,那么这可能是一个很好的起点。
递归:它为何重要
递归是令人畏惧的;“魔法”这个词经常被提及。对于初学者的建议很多。我曾读到有人建议初学者“相信魔法”——或者类似的说法。你可能会被告知学习调用栈的工作原理。当然,这很有用,甚至是基础的:但不是成为熟练甚至舒适使用递归的最佳方式。我知道这些建议对我没有真正奏效。真正奏效的是,你猜对了,练习。我可能不是递归的高手。但确实,现在我对使用递归更加游刃有余。事实上,我有时现在甚至会挣扎于不编写递归函数,即使我可以使用更简单、更高效的解决方案。
我怀疑人们在递归上遇到困难,是因为它某种程度上(错误地)暗示在幕后发生了无限回归。它似乎像是当两面镜子相对放置时发生的现象:第一面镜子反射第二面镜子的反射,第二面镜子反射第一面镜子的反射,依此类推……这是一种效果——顺便提一下——被许多艺术家和设计师巧妙地运用了。但这根本不是实际发生的情况。
递归的函数是一个调用自身的函数。例如,这里有两个(略有不同)函数,它们接受一个整数数组或列表并返回其和,分别用 Javascript 和 Python 编写:
function sumAllNums(listNums) {
if (listNums.length === 0) return 0;
return listNums.shift() + sumAllNums(listNums);
}
console.log(sumAllNums([1, 2, 3, 4]));
// 10
def sum_all_nums(lst):
if len(lst) == 0:
return 0
return lst[0] + sum_all_nums(lst[1:])
print(sum_all_nums([1, 2, 3, 4]))
# 10
两个函数都将:
-
检查输入是否为空,如果为空则返回 0(基本情况)
-
否则,它们将把输入的第一个项目添加到调用该函数时剩余输入的结果中(除了第一个项目之外的所有项目)(即递归调用)。
递归函数会无限调用自身,除非它达到了基本情况。当发生这种情况时,递归调用停止 — 这就是我们避免无限循环的原因。
这可能都很好,但我为什么要熟练掌握编写递归函数呢?毕竟,你可以用 while 循环中的迭代来完成任何可以递归编写的东西。为什么要为这个看似神奇的工具而挣扎?这是一个合理的问题。脱口而出,我能想到至少以下三个理由:
-
算法和数据结构:许多基础算法本质上是递归的(想想“分治”)。一些数据结构以递归方式表示最佳(想想图或树)
-
即使在机器学习领域,递归结构和算法也比人们想象的要常见**(更不用说人工智能**,它们无处不在)。例如,决策树的实现是本质上递归的。
-
函数式编程:有人说函数式语言的受欢迎程度正在慢慢增长。这可能是真的,也可能不是。确实,越来越多的“主流”语言如 Python 或 Javascript 现在支持函数式编程。此外,一些广泛使用的库和框架(如 React)在很大程度上基于函数式实践。鉴于函数式范式没有赋值的概念(稍后会详细介绍),递归对它至关重要。
-
理解:递归迫使你将问题简化为其他更小的问题。这是你作为程序员可能发展出的最有价值的技能之一。
不幸的是,我想不到一个专门用于练习递归的全面资源。这就是The Little Schemer的作用所在。
学习递归的最佳书籍
我的 TLS 版本 — 还有一杯浓缩咖啡(我来自意大利!)[作者提供的图片]
根据标题,《The Little Schemer》(以下简称 TLS)应该是一本关于编程语言 Scheme 的书,它是组成 LISPs 家族的众多语言之一。那么为什么有人会特别想学习 LISP 或 Scheme 呢?问题是,标题并不准确。Scheme 是一种非常适合教授计算机科学某些基础知识的语言²——对于 TLS 来说,你需要的语法是如此稀疏,你可以在几分钟内迅速掌握。你可以更安全地将其视为一种教学工具,而不是一门成熟的语言³。
说到教学法:这本书采用了奇特的风格。它以两列的形式书写,类似于学生和老师之间的苏格拉底式对话。它是这样开始的:
这是否是一个原子? 原子
它不给你定义——它向读者提出问题(通常以练习的形式),然后提供答案(或解决方案)。这需要一些适应,但非常有效。
TLS 的奇特风格 [作者提供的图片]
递归与这一切有什么关系?好吧,在 TLS 中,作者使用 Scheme 的方式不支持变量赋值或迭代。没有 while
或 for
循环。这意味着,如果你必须编写一个函数,例如检查某个数字是否包含在列表或数组中,你不能像在 Python 中那样做:
def is_contained(num, list_nums):
for ix in range(len(list_nums)):
if list_nums[ix] == num:
return True
return False
print(is_contained(1, [2, 3, 4, 1]))
# True
相反,你必须以递归的方式思考:
-
有一个基本情况,即列表将是空的,函数应该返回 False
-
否则,你选择第一个列表项并检查它是否等于给定的数字;如果匹配,函数返回 True。如果不匹配,(递归地)调用函数处理列表的其余部分。
这就是你在 TLS 中编写所有内容的方式。Scheme 仅在两个基本的语法元素上操作:
-
原子:一个或多个字母数字字符
-
列表:零个或多个被括号包围的原子或列表。
要编写如上所述的函数,你只需要:
-
一个编程构造来测试两个表达式是否相同
-
另一个测试,用于检查列表是否空
-
选择器 用于列表的第一个元素和列表的其余部分
-
表达条件(例如
if
)的一种方式
对于更复杂的函数,你还需要一个构造器来构建列表。加上一些定义函数的方法,仅此而已。这就是为什么几乎所有的语法都在第一章中介绍,在不到 10 页的简洁内容中(唯一的例外是列表构建构造器 cons,它在第三章中介绍)。前三章带你了解许多更简单的递归函数。
当你到达第四章时,你应该已经对编写操作列表的递归函数的基本设计模式有所了解:例如上面提到的函数,它们检查特定项是否是列表的成员。或者,替换列表中给定项的第一个(或所有)出现的函数。
随后,这些知识应用于从基本原理构建基本的算术操作。同样,这通过对数字进行递归以及使用提供的原语(这些原语将数字递增和递减 1)来实现。例如,两个数字n和m的加法定义为:
-
基本情况:如果m为零,返回n
-
否则,递归返回将加法函数应用于递增的n和递减的m的结果。
第五章和第六章介绍了对任意深度嵌套列表的递归函数:这些列表可能包含列表,而这些列表又可能包含列表,依此类推。基本上,你需要重写前几章中的所有函数,以便它们在“更深”的情况下也能工作。
这些知识在第六章中得到了具体应用,你将在其中编写一个用于算术表达式的解释器。让我强调:我们已经阅读了 100 页的内容。我们从没有 Scheme 知识和最简语法开始——而现在我们正在构建一个评估我们定义的操作的解释器。如果你对编程的更理论方面感兴趣,你可能会发现这非常有价值和激动人心。
第七章专注于集合论构造、关系和函数,这些内容同样留给读者在阅读师生对话的过程中发现。
第八章可能是本书中最令人不满意的章节:它相当陡峭,而且很难看出这里引入的一些构造的意义。其关键似乎是引入了延续,但我必须承认我在这一章中感到困难(显然,许多人也有同样的感受)。
与前一章类似,第九章是具有挑战性的:在几页中介绍了许多基础概念,这些概念引出了对应用顺序 Y 组合子的讨论。我非常喜欢这一章,但我花了很多次尝试和一些在线资源的帮助(见下文)才理解它。顺便说一下,如果你曾经想知道 Y 组合子到底是什么(既然我们讨论递归的话题):它是(非常粗略地说)在没有函数命名方式的语言中如何定义递归(因此,这种语言中的所有函数都是匿名的——例如 Python 中的lambda函数)。能够理解 Y 组合子是一次丰富的经历,对我来说,它是本书的亮点之一。
最后一章将所有内容整合在一起,你最终会用 Scheme 编写一个 Scheme 解释器 在 Scheme 中。让它沉淀下来:只用几个原语,仅用递归,你正在通过引导语言本身来编写一个语言的解释器。
我希望我能够传达我在阅读 TLS 时的兴奋感。也希望你能与我分享你的经历。
额外资源
-
我有一个 GitHub 仓库(正在进行中!),在这里我发布了笔记、解决方案和一个相当全面的 Racket 测试套件,链接在此
-
另一个我喜欢的 GitHub 仓库(同样是 Racket):
github.com/bmitc/the-little-scheme
-
对这本书的一个负面(但不完全不相关)评论:
inventwithpython.com/blog/2018/12/09/book-review-the-little-schemer/
)以平衡我的热情 -
如果你遇到困难,关于 Y 组合子的一个优秀博客文章
[1] Friedman, Daniel, P, Felleisen, Matthias [《小 Scheme》], 麻省理工学院出版社,剑桥,MA,1986 年,1996 年第四版 (mitpress.mit.edu/9780262560993/the-little-schemer/
)。最初出版为 《小 LISPer》。
[2] 事实上,《计算机程序的结构与解释》是有史以来最受推崇的计算机科学书籍之一,结构与解释计算机程序 使用了 Scheme。
[3] 这并不重要,但我应该提到我在 TLS 中的练习实现不是用 Scheme,而是用 Racket,它基本上是 Scheme 的继任者。
理解回归树所需的唯一指南
原文:
towardsdatascience.com/the-only-guide-you-need-to-understand-regression-trees-4964992a07a8
关于决策树的完整指南,包括从零开始的逐步实现和使用 Scikit-Learn 的动手示例
·发表于 Towards Data Science ·阅读时长 25 分钟·2023 年 4 月 4 日
–
构建一棵树 - 作者提供的图像
目录
-
简介
-
回归决策树:背后的理论
-
从理论到实践——从零开始的决策树
-
动手示例——从零开始实现与 Scikit-learn 决策树对比
-
总结
-
参考文献
-
附录 / 代码
1. 简介
决策树自 1960 年代以来一直存在。尽管它们是最简单的机器学习算法之一,但在解决问题时被证明非常有效。它们最大的优势之一是易于解释,使得那些没有技术背景的人也能轻松理解。在许多行业中,数据科学家仍需建立对机器学习用例的信任。像决策树这样的可解释基准模型可以帮助减少一些怀疑。如果有人愿意付出努力,他们甚至可以追踪学到的树的分支,并尝试找到他们已经知道的关于问题的模式。
另一方面,我们很快就会遇到简单决策树在复杂问题上的极限。从理论上讲,我们可以用适当大小的树来建模任何(复杂的)数据分布,但这些模型在应用于新数据时往往无法很好地泛化——它们对训练数据集过拟合。然而,决策树在机器学习中始终发挥着重要作用。
决策树的一些弱点已经随着树集成技术的进步逐渐得到解决或至少得到缓解。在树集成中,我们不是学习一棵决策树,而是学习一系列树,并最终将它们组合成一个集成。如今我们区分了包装(bagging)和提升(boosting)算法。
-
在袋装中,多个决策树在不同的自助样本(随机选择的带有替代的子集)上进行训练。每棵决策树是独立训练的,最终的预测是通过平均所有个体树的预测来得出的。袋装方法,特别是随机森林算法是由 Leo Breiman 开发的。
-
在提升中,决策树是按顺序训练的,每棵树都被训练以纠正前一棵树所犯的错误。训练数据被加权,对前一棵树错误分类的样本给予更高的权重。
尽管随机森林仍然发挥着重要作用,但今天主要是提升算法在数据科学竞赛中表现最佳,且常常优于袋装方法。最知名的提升算法包括AdaBoost、XGBoost、LightGBM 和 CatBoost。自 2016 年以来,它们的受欢迎程度持续增长。
基于树的算法类型 — 图片由作者提供
虽然决策树的概念已经被认识并积极应用了几十年,但提升方法相对“新颖”,只有在 2014 年 XGBoost 发布后才逐渐获得重要性。
基于树的算法的演变 — 图片由作者提供(灵感来源于(Chow, 2021; Swalin, 2019))
在 XGBoost 概念首次发布后的几个月,Higgs Boson Challenge 在 Kaggle 上用它赢得了比赛。XGBoost 基于许多概念,这些概念汇聚成一个极其有效的算法。XGBoost 的核心当然是梯度提升原理,但 XGBoost 远不止于此。XGBoost 包括各种优化技术,使得 XGBoost 在训练过程中极其高效和快速。特别是对于小到中等规模的结构化数据集,像 XGBoost、LightGBM 和 CatBoost 这样的梯度提升框架继续发挥重要作用。
这不仅仅是我的观点。一个好的指标是 Kaggle 竞赛及其获胜解决方案。
在文章《竞争性机器学习的现状》中, mlcontests.com 评估了 2022 年在 Kaggle 和其他竞赛平台上的 200 多个数据竞赛。根据报告,梯度提升决策树(GBDT)仍然是 2022 年表格数据用例的首选方法,并且能够赢得大多数此类领域的竞赛。(Carlens, n.d.)
除了梯度提升算法一再展示出的良好性能外,决策树或树集成的最大优势是速度。一般而言,梯度提升框架在训练速度上比神经网络更快,这在许多现实世界的问题中可能是一个重要因素。
通常在机器学习项目开始时数据集并不明确。工作的重要部分是数据集的汇编和相关特征的提取。如果我们更改数据集、添加新列或仅稍微更改将分类值转换为数值的方式,我们需要衡量这样做是否改善了整体过程。在这个过程中,我们可能会训练几百次模型。因此,更快的训练时间可以决定性地影响整个机器学习用例的开发过程时间。
机器学习项目是迭代的,而不是线性的 — 图片来源:作者
下图显示了机器学习管道中的各个步骤。如果我们在训练模型之前对过程中的某个小细节进行更改,我们必须重新评估整个过程和结果模型。
机器学习管道 — 图片来源:作者
文章内容:
本文旨在奠定基础,深入探讨各种基于决策树的树型集成算法。决策树的概念非常直观,易于理解。乍一看,XGBoost、CatBoost 和 LightGBM 看起来复杂一些。但如果你仔细观察,XGBoost 只是不同概念的组合,而这些概念本身又很容易理解。
一旦你理解了随机森林和梯度提升框架,你将能够解决各种数据科学问题。从分类到回归再到异常检测。
知识关于深度学习框架如 Pytorch、TensorFlow 等在几乎所有数据科学职位中扮演如此核心的角色,这有些荒谬。在许多领域,你将花费大部分时间来收集数据、准备数据和提取特征。如果你有了合适的特征集,模型的创建本身通常是相当简单的。如果你主要处理表格数据,你可能会通过袋装和提升算法走得很远。
如果你想一次性下载文章中使用的代码并作为参考,你可以在 github 上找到使用的代码片段。你也可以在附录中找到我们将在本文中构建的决策树算法的代码,位于文章底部。
2. 回归的决策树:背后的理论
决策树是最简单的机器学习算法之一。它们的工作方式相对容易解释。
作为人类,我们尝试通过将复杂问题分解为相对简单的是或否决策来解决问题。当我们想买一辆新车时,我们浏览所有能找到的汽车网站。过一段时间后,我们对某款车的价格有了感觉。我们感觉到奢侈品牌和便宜制造商之间的成本差异有多大,以及与较小的 100 hp 引擎相比,150 hp 引擎的额外费用有多少,等等。
一步步地,我们的大脑记住了某些特征组合的球 park 值。当我们停在汽车经销商那里,逐一查看汽车的特征时,就像是在决策树上向下移动,直到我们认为得到了一个公平的价格。
一个简单的回归树预测汽车价格 — 图像由作者提供
注意: 在我们深入了解决策树的构建之前,需要提到有不同的决策树算法。一些流行的算法包括 ID3、C4.5、C5.0 和 CART(Google Developers, 2017)。Scikit-learn 的实现基于 CART,该算法由 Leo Breiman 等人于 1984 年首次发布。Leo Breiman 是一位美国统计学家,他塑造了“bagging”的方法,开发了随机森林,从而对基于树的算法的进一步发展做出了重要贡献。
我们如何开始构建决策树?
要开始决策树构建过程,我们需要回答三个问题:
-
我们从哪个特征开始? - 我们在每个节点沿一个维度划分数据集。在这个例子中,我们使用特征 x_1 进行划分。由于我们不想仅仅选择随机特征,我们会提前搜索出划分数据集带来最大增益的特征。(在这种情况下,我们通常谈到所谓的信息增益。我们可以以不同的方式定义信息增益,在回归中我们通常使用平方误差)。
-
最佳的阈值来划分数据是什么? - 类似于第一步,当我们选择一个特征时,我们仍然需要知道我们想用什么阈值来划分数据集。换句话说,就是在维度上的哪个位置我们想要划分数据集。
-
我们什么时候停止划分数据集? - 如果我们在某个点不停止划分过程,决策树将继续进行,直到每个叶子节点中只有一个样本点。为了避免过拟合,我们需要一些标准来决定划分数据集的深度以及何时停止划分过程,以避免模型变得不必要的复杂。
我们必须回答的三个问题 — 图像由作者提供
我们再次使用汽车价格预测的例子。首先,我们需要选择一个特征并分割数据集。我们选择一个特征和一个阈值,将数据集分割成左侧和右侧部分,并计算平均价格。这给了我们第一个节点。如果我们现在停止,我们将拥有一个只有一个层级的简约决策树——所谓的决策桩。
然而,我们不想从随机分割开始,而是从“最佳可能”的分割开始。
但是,如何定义“最佳”分割呢?
我们需要定义一个度量标准,帮助我们评估分割的效果。
在回归问题中,常用的损失函数有 平均绝对误差 或 均方误差。通常,我们可以在不同的度量标准之间进行选择。要了解 scikit-learn 是如何计算每次分割的性能的,我们可以直接查看文档或源代码。
访问源代码的最简单方法是通过代码编辑器。
如果你还没有安装 scikit,你可以通过以下 pip 命令进行安装:
pip install scikit-learn
我在大多数项目中使用 Visual Studio Code。如果我创建一个新的笔记本或 Python 文件并导入相应的模块,Visual Studio 会提供一个直接链接到其背后的源代码。在左侧的图片中,你可以看到 Visual Studio Code 中的整个情况。
作者截图
-
创建一个新文件,在我的例子中是“tree_algorithms.py”,并导入回归树模块“sklearn.tree.DecisionTreeRegressor”。
-
按下“Ctrl”并点击相应模块,你将直接跳转到源代码的相应部分。
你也可以在 scikit-learn 的文档中找到源代码。右侧可以看到在 scikit-learn.org 上的整个情况。每个类和函数还有一个指向 Github 上源代码的链接。
如果我们深入到 DecisionTreeRegressor 的源代码中,我们会看到它被定义为一个用以下值初始化的类:
def __init__(
self,
*,
criterion="squared_error",
splitter="best",
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.0,
max_features=None,
random_state=None,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
ccp_alpha=0.0,
):
我们将逐渐深入了解每个超参数的作用。我们一开始感兴趣的是分割标准,即决策树在构建过程中如何确定如何分割数据集。
作者截图
代码中还包含了我们可以选择的标准的简短描述。
Scikit-learn 让我们可以在以下选项中选择:
-
squared_error
-
friedmann_mse
-
绝对误差
-
泊松
默认值是“squared_error”。文档中将其描述如下:
“squared_error” 等同于方差减少,并使用每个终端节点的均值来最小化 L2 损失
因此,我们尝试在终端节点中最小化 均方误差。
假设我们有一个简单的二维数据集,只有 x_1 作为唯一的输入特征和 y 作为目标变量。对于这个简单的例子,我们不需要决定哪个特征最适合分割数据集,因为数据集中只有一个特征。因此,在根节点,我们使用 x_1 将数据集分成两半。
在下图中,您可以找到一个简单的二维数据集。数据集的两个部分是我们的子节点。在我们进行第一次分裂时,这两个子节点是叶节点或终端节点(不会进一步分裂的节点)。
在所示的情况下,我们在 x_1=2 处分割数据集。
如果我们用它来进行预测,树现在会预测什么值?
我们必须为每个终端节点定义一个值,这样就代表了决策树的可能预测值。我们以最简单的方式计算这个预测值 y_pred,我们计算左节点的平均值 (这里: y_pred_left = 9) 和右节点的平均值 (这里: y_pred_right = 5)。
分割数据集 — 图片由作者提供
我如何找到最佳的分裂数据集的阈值?
在所示的示例中,我们选择了 x_1 = 2 作为阈值。但这是否是最优情况?
为了评估分裂的性能,我们计算残差,即 y_predict 与每个样本的 y 之间的差异。更准确地说,我们计算 L2 损失函数,即残差的平方和。
如何计算左叶子和右叶子的预测值 — 图片由作者提供
为了得到桩模型的性能值,我们分别计算两侧的偏差(l2 损失),然后通过包括两半样本的数量来计算加权总体损失。
我们反复进行这个过程,尝试不同的阈值(见图片)。在我们的例子中,当我们选择 x_1 = 5 作为分裂阈值时,加权平方误差最小:
父节点和子节点的 MSE 计算 — 图片由作者提供
我们的算法如何找到最小误差?
决策树以非常简单的方式执行这一操作,它定义了一种迭代方法,尝试不同的阈值。因此,我们定义了一个可能的阈值/分裂值列表,并计算列表中每个可能阈值的均方误差。
-
步骤 1 - 定义可能的分裂阈值列表: 我们通过排序值并计算滚动平均值来定义所有可能的分裂值。因此,如果 x_1 = 2 和 x_2 = 3,那么可能阈值列表中的第一个阈值是 2.5。
-
步骤 2: 在下一步中,我们需要找到最小化平方误差的阈值来构建节点。我们开始迭代所有阈值,拆分数据集,并计算右节点和左节点的 MSE。
不同阈值下均方误差的迭代计算 — 图片由作者提供
让我们用实际数据集来尝试一下。
加载现实世界的数据集
为了在实际数据集上演示刚才描述的步骤,我们从 UCIMachineLearning 下载了汽车数据集。该数据集包含许多属性,如马力、尺寸、燃料类型等,用于详细描述汽车。我们感兴趣的目标变量是价格。(UCI Machine Learning Repository: 汽车数据集,无日期。)[许可证: CC0: 公共领域]
def load_auto_data_set():
# Load the automobile data set from UCI.edu
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/autos/imports-85.data>'
df = pd.read_csv(url, header=None)
# Name columns
df.columns = ['symboling', 'normalized_losses', 'make', 'fuel_type', 'aspiration', 'num_doors', 'body_style', 'drive_wheels', 'engine_location','wheel_base','length','width','height', 'curb_weight','engine_type','num_cylinders','engine_size','fuel_system','bore','stroke', 'compression_ratio','horsepower','peak_rpm','city_mpg','highway_mpg','price']
# Filter for lines where power and price are available
df = df[(df.horsepower != '?')]
df = df[(df.price != '?')]
# Filter for lines where power and price are available
df['horsepower'] = df['horsepower'].astype(int)
df['price'] = df['price'].astype(int)
# Define the last column of the data frame as y and the rest as X
self.y = self.df.iloc[:, -1]
self.X = self.df.iloc[:, :-1]
return df, X, y
之后,我们执行刚才描述的步骤。以下代码片段使用所选特征selected_feature和定义的阈值threshold来分割数据集(X_parent, y_parent)。
该图展示了左子节点和右子节点的样本及观察值的平均值。如果我们现在停止,子节点将成为树的叶子节点,树的预测值将由两个部分的计算均值表示。
class NodePlot():
def __init__(self, X_parent, y_parent, threshold, selected_feature):
self.selected_feature = selected_feature
self.x_column = X_parent[self.selected_feature]
self.y_parent = y_parent
self.data_set = np.column_stack((self.x_column, y_parent))
self.threshold = threshold
# define a list with all observations of the left and right leaf
self.left_y = self.data_set[self.data_set[:, 0]<self.threshold][:, 1]
self.left_x = self.data_set[self.data_set[:, 0]<self.threshold][:, 0]
self.right_y = self.data_set[self.data_set[:, 0]>=self.threshold][:, 1]
self.right_x = self.data_set[self.data_set[:, 0]>=self.threshold][:, 0]
# calculate the mean of the observations for the left and right leaf
self.parent_y_mean = np.mean(self.y_parent)
self.left_y_mean = np.mean(self.left_y)
self.right_y_mean = np.mean(self.right_y)
# calculate the weighted mean squared error
self.parent_mse = np.mean((y_parent - self.parent_y_mean)**2)
mse_l = np.mean((self.left_y - self.left_y_mean)**2)
mse_r = np.mean((self.right_y - self.right_y_mean)**2)
# calculate the number of instances in the parent and child nodes
n_l = len(self.left_y)
n_r = len(self.right_y)
n = len(self.data_set)
# calculate the weighted mse for child nodes
self.child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
def plot_split(self):
plt.rcParams['font.size'] = '16'
sns.set_style("darkgrid", {"axes.facecolor": ".9"})
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=self.left_x,
y=self.left_y,
mode="markers",
name="Data set: left node",
line=dict(color="grey")
)
)
fig.add_trace(
go.Scatter(
x=self.left_x,
y=np.linspace(self.left_y_mean, self.left_y_mean, len(self.left_x)),
mode="lines",
name="Right node prediction",
line=dict(color="black")
)
)
# create go.scatter plot with black line
fig.add_trace(
go.Scatter(
x=self.right_x,
y=self.right_y,
mode="markers",
name="Data set: right node",
#line=dict(color="#ffe476")
line=dict(color="black")
)
)
fig.add_trace(
go.Scatter(
x=self.right_x,
y=np.linspace(self.right_y_mean, self.right_y_mean, len(self.right_x)),
mode="lines",
name="Left node prediction",
line=dict(color="black", dash='dot')
)
)
fig.add_trace(
go.Scatter(
x=[self.threshold, self.threshold],
y=[min(self.y_parent), max(self.y_parent)],
mode="lines",
name="MSE of parent node",
line=dict(color="black", dash='dashdot')
)
)
# update title in go.Figure
fig.update_layout(title="Data set", xaxis_title=self.selected_feature, yaxis_title=self.y_parent.name)
fig.show()
图片由作者提供
由于我们不想在数据集中的任何地方进行分割,而是选择“最佳”点,因此我们按上述描述进行迭代。我们使用节点绘图类来计算多个阈值的残差。
selected_feature = "horsepower"
list_of_mse_childs = []
list_of_mse_parent = []
thresholds = X.sort_values(by=["horsepower"])["horsepower"].unique()
for threshold in thresholds:
NodePlot = helper_functions.NodePlot(
X_parent = X,
y_parent = y,
threshold = threshold,
selected_feature = "horsepower"
)
list_of_mse_childs.append(NodePlot.child_mse)
list_of_mse_parent.append(NodePlot.parent_mse)
def plot_threshold_evaluation(thresholds, mse_parent_list, mse_list):
# create figure
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=thresholds,
y=mse_list,
mode="lines",
name="MSE after split",
line=dict(color="black")
)
)
fig.add_trace(
go.Scatter(
x=thresholds,
y=mse_parent_list,
mode="lines",
name="MSE of parent node",
line=dict(color="black", dash='dot')
)
)
fig.add_trace(
go.Scatter(
x=[threshold,threshold],
y=[min(mse_list), max(mse_list)],
mode="lines",
name="Chosen threshold",
line=dict(color="black", dash='dashdot')
)
)
# update title in go.Figure
fig.update_layout(title="Evaluate", yaxis_title='MSE')
fig.show()
return fig
# plot the just calculated MSE values for different thresholds
plot_threshold_evaluation(
thresholds = thresholds,
mse_parent_list = list_of_mse_parent,
mse_list = list_of_mse_childs,
threshold = 100
)
图片由作者提供
我们计算每个可能分割的父节点和子节点的平方误差。对于决策树,我们寻求最大信息增益。使用平方误差作为分割标准,我们简单地将信息增益计算为父节点的 MSE 与子节点加权 MSE 之间的差异。
在图表中,信息增益的最大值出现在 120 小时。
图片由作者提供
在决策树中,我们不断重复刚才描述的步骤。
注意:决策树被归类为贪心算法。贪心算法在选择过程中逐步选择承诺最佳结果的状态。做出决策时不考虑之前和之后的决策。
程序什么时候结束?
决策树在训练模型时不会做很多假设。例如,线性回归恰恰相反,当线性回归算法训练模型时,它仅允许模型具有一种可能的形状,即空间中的直线或平面。因此,当我们使用线性回归作为学习算法时,我们直接假设我们的问题遵循线性行为。
参数模型(线性回归)与非参数模型(回归树)— 作者提供的图像
决策树在学习过程中非常灵活。这些模型被称为“非参数模型”。当模型的参数数量没有提前确定时,就称为非参数模型。线性回归有一个明确定义的参数数量,即斜率和偏移量。这显著限制了训练过程中的自由度。(Géron,2022)
因此,决策树往往容易过拟合。为了避免这种情况,我们需要引入限制训练过程自由度的超参数,即所谓的正则化超参数。
一个常用的正则化参数是 max_depth,即树的最大深度。其他的包括:
-
min_samples_split(节点需要的最小样本数,以便进行拆分)
-
min_samples_leaf(每个叶子节点必须具有的最小样本数)
-
min_weight_fraction_leaf(类似于 min_samples_leaf,但我们定义的是整个数据集的一个比例,而不是一个具体的数字)
-
max_leaf_nodes(叶子节点的最大数量)
-
max_features(在每次分裂时评估的最大特征数量)。
在实际模型构建后,我们仍然可以修剪树,以避免模型不必要的复杂性。
什么是剪枝?
这项技术包括将树生长到其全部大小,然后删除那些对验证数据集的准确性没有提升的分支或子树。这是通过计算修剪子树前后的误差变化,并与一个阈值进行比较来完成的。如果误差变化不显著,则会修剪子树。由于我暂时不想深入探讨这个问题,我将在以下简单示例中省略它。
接下来,我将向你展示如何从头开始构建一个基本版本的回归树。
3. 从理论到实践 - 从头开始构建决策树
为了灵活使用回归树,我们将代码放入一个新的模块中。我们创建一个新的 Python 文件,将所有与我们算法和学习过程相关的代码放入其中。在这个文件中,我们定义一个名为“RegressionTree”的新类,并用作为训练过程约束的超参数进行初始化。
如前所述,处理决策树的最大挑战之一是过拟合的风险。为了降低这一风险,并确保我们的模型能够很好地推广到新数据,我们引入了正则化参数,这些参数在某个点指导和停止学习过程。
我们在简化版决策树中使用的正则化参数(或停止准则)有以下两个:
min_samples_split
- 定义一个节点需要的最大样本数量,以便进一步拆分。合适的值取决于数据集的类型和大小。如果选择得当,它可以防止过拟合。在 scikit-learn 中,默认值设置为 2 个样本。
max_depth
- 最大深度决定了树最多可以有多少层。如果其他停止准则如 min_sample_split 在达到此深度之前阻止了树的进一步生长,则可能无法达到此树的大小。Scikit-learn 默认将值设置为“None”,因此默认情况下最大深度没有限制。
Scikit-learn 还包括一些额外的停止参数,如 min_samples_leaf, min_weighted_fraction, max_leaf_nodes, or max_features,这些参数默认为未设置,我暂时会忽略它们。
我们为每个回归器需要的一个函数是 fit 函数,它启动训练过程。输入变量是一个多维数组(X),包含输入特征。y 是一个一维数组,描述目标变量。
除了我们的回归器(RegressionTree)外,我们还定义了一个第二个类(Node),通过它设置和存储树的每个节点所具有的参数。
class Node():
def __init__(
self,
feature=None,
threshold=None,
left=None,
right=None,
value=None
):
self.feature = feature
self.threshold = threshold
self.left = left
self.right = right
self.value = value # is it a leave node?
def is_leaf_node(self):
return self.value is not None
class RegressionTree():
def __init__(
self,
min_samples_split=2,
max_depth=100):
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.root = None
def fit(self, X, y):
self.root = self._grow_tree(X, y)
fit 函数使用辅助函数 _grow_tree(x, y) 一步步地生长树,直到达到停止准则中的一个。
在分割节点之前,我们检查是否满足任何一个停止准则。在简化的示例中,我们只有两个停止准则:
(1) depth >= self.max_depth: 是否达到树的最大深度?
(2) n_samples < self.min_samples_split: 节点中的样本数是否大于 min_samples_split?
如果任一条件为真,则该节点是终端节点,我们需要计算的唯一内容是均值(np.mean(y))。
如果两个条件都不为真,我们将进一步分割数据集。我们首先定义考虑哪些特征进行分割。在我们简化的情况下,我们不会限制用于分割的列。我们使用 X 中的所有特征(feat_idxs)。
对于实际的分割,我们定义了另一个辅助函数 _best_split,我们将正在查看的节点的 x 和 y 值传递给它。
我会稍后详细介绍 _best_split 的作用,但正如名称所示,_best_split 会以所选特征(best_features)和我们分割数据集的阈值(best_threshold)的形式返回“最佳”分割。
我们使用这些信息来实际分割数据集并将其作为我们树的一个节点存储。
在我们跳出函数之前,我们会再次调用**_grow_tree**来处理分支的两个部分。
def _grow_tree(self, X, y, depth=0):
# check the stopping criteria
n_samples, n_feats = X.shape
if (depth>=self.max_depth or n_samples<self.min_samples_split):
leaf_value = np.mean(y)
return Node(value=leaf_value)
feat_idxs = np.random.choice(n_feats, n_feats, replace=False)
# find the best split
best_thresh, best_feature = self._best_split(X, y, feat_idxs)
# create child nodes
left_idxs, right_idxs = self._split(X[:, best_feature], best_thresh)
left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth+1)
right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth+1)
return Node(best_feature, best_thresh, left, right)
唯一未解答的问题是算法如何确定最佳的分割。
如前所述,我们计算所谓的信息增益。在我们的例子中,我们将信息增益定义为均方误差的减少。节点本身及其结果子节点的误差或残差是通过计算每个节点中目标变量 y 的平均值与节点中样本的实际 y 值之间的差异来得到的。
该函数逐个遍历每个特征。
-
我们为每个特征计算一组可能的阈值,作为所有观察值的移动平均值。
-
然后,我们遍历列表中的每个阈值,拆分数据集,并计算子节点的加权均方误差。
-
然后,我们检查计算得到的 MSE 是否是迄今为止计算出的最小 MSE,如果是的话,我们将 feature_idx 和 threshold 保存为最优(在best_feature_idxs和best_thresholds中)。
def _best_split(self, X, y, feat_idxs):
y_mean = np.mean(y)
residuals_y = (y - y_mean)**2
y_mse = np.mean(residuals_y)
best_feature_ixd, best_threshold = None, None
lowest_mse = y_mse
for feat_idx in feat_idxs:
# define possible thresholds for the split
X_column = X[:, feat_idx]
thresholds = np.convolve(np.sort(X_column), np.ones(2)/2, mode='valid')
for threshold in thresholds:
# getting the left and right nodes
left_idxs, right_idxs = self._split(X_column, threshold)
# calculate the weighted avg. mse of children
n = len(y)
n_l, n_r = len(left_idxs), len(right_idxs)
mse_l = self._squared_error(y[left_idxs])
mse_r = self._squared_error(y[right_idxs])
child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
if lowest_mse > child_mse:
lowest_mse = child_mse
best_feature_ixd = feat_idx
best_threshold = threshold
return best_feature_ixd, best_threshold
我们在上述几个部分中已经多次使用的两个函数是**_split和_squared_error**。
def _split(self, X_column, split_thresh):
left_idxs = np.argwhere(X_column <= split_thresh).flatten()
right_idxs = np.argwhere(X_column > split_thresh).flatten()
return left_idxs, right_idxs
def _squared_error(self, y):
# calculate the mean value for all observations
y_mean = np.mean(y)
# calculate the residuals to y_mean
mean_squared_error = np.mean((y - y_mean)**2)
return mean_squared_error
唯一需要的就是predict()函数。为此,我们使用_traverse_tree。
使用循环函数,我们逐个遍历刚构建的树。如果到达叶子节点,_traverse_tree返回存储的节点值。
def predict(self, X):
return np.array([self._traverse_tree(x) for x in X])
def _traverse_tree(self, x, node):
if node.is_leaf_node():
return node.value
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
return self._traverse_tree(x, node.right)
就这样,完整的决策树回归器定义为:
import numpy as np
from collections import Counter
from sklearn.metrics import mean_squared_error
from collections import Counter
class Node():
def __init__(
self,
feature=None,
threshold=None,
left=None,
right=None,
value=None
):
self.feature = feature
self.threshold = threshold
self.left = left
self.right = right
self.value = value # is it a leave node?
def is_leaf_node(self):
return self.value is not None
class RegressionTree():
def __init__(
self,
min_samples_split=2,
max_depth=100):
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.root = None
def fit(self, X, y):
self.root = self._grow_tree(X, y)
def _grow_tree(self, X, y, depth=0):
# check the stopping criteria
n_samples, n_feats = X.shape
if (depth>=self.max_depth or n_samples<self.min_samples_split):
leaf_value = np.mean(y)
return Node(value=leaf_value)
feat_idxs = np.random.choice(n_feats, n_feats, replace=False)
# find the best split
best_feature_ixd, best_threshold = self._best_split(X, y, feat_idxs)
# create child nodes
left_idxs, right_idxs = self._split(X[:, best_feature_ixd], best_threshold)
left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth+1)
right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth+1)
return Node(best_feature_ixd, best_threshold, left, right)
def _best_split(self, X, y, feat_idxs):
y_mean = np.mean(y)
residuals_y = (y - y_mean)**2
y_mse = np.mean(residuals_y)
best_feature_ixd, best_threshold = None, None
lowest_mse = y_mse
for feat_idx in feat_idxs:
# define possible thresholds for the split
X_column = X[:, feat_idx]
thresholds = np.convolve(np.sort(X_column), np.ones(2)/2, mode='valid')
for threshold in thresholds:
# getting the left and right nodes
left_idxs, right_idxs = self._split(X_column, threshold)
# calculate the weighted avg. mse of children
n = len(y)
n_l, n_r = len(left_idxs), len(right_idxs)
mse_l = self._squared_error(y[left_idxs])
mse_r = self._squared_error(y[right_idxs])
child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
if lowest_mse > child_mse:
lowest_mse = child_mse
best_feature_ixd = feat_idx
best_threshold = threshold
return best_feature_ixd, best_threshold
def _split(self, X_column, split_thresh):
left_idxs = np.argwhere(X_column <= split_thresh).flatten()
right_idxs = np.argwhere(X_column > split_thresh).flatten()
return left_idxs, right_idxs
def _squared_error(self, y):
# calculate the mean value for all observations
y_mean = np.mean(y)
# calculate the residuals to y_mean
mean_squared_error = np.mean((y - y_mean)**2)
return mean_squared_error
def predict(self, X):
return np.array([self._traverse_tree(x, self.root) for x in X])
def _traverse_tree(self, x, node):
if node.is_leaf_node():
return node.value
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
return self._traverse_tree(x, node.right)
4. 实战示例 — 从头实现与 Scikit-learn 决策树
加载数据集
对于测试,我们使用之前作为示例的数据集,即汽车数据集。首先,我们从 uci.edu 加载数据集。然后,我们选择一些属性进行第一次简单测试。在以下示例中,我选择:
-
车轮基距
-
长度
-
宽度
-
高度
-
制造商
对于输入向量 X。
由于属性“make”包含字符串,我们使用 OneHot Encoding 将其转换为数值特征。
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
def load_auto_data_set():
# Load the automobile data set from UCI.edu
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/autos/imports-85.data'
df = pd.read_csv(url, header=None)
# Name columns
df.columns = [
'symboling', 'normalized_losses', 'make',
'fuel_type', 'aspiration', 'num_doors',
'body_style', 'drive_wheels', 'engine_location',
'wheel_base','length','width','height',
'curb_weight','engine_type','num_cylinders',
'engine_size','fuel_system','bore','stroke',
'compression_ratio','horsepower','peak_rpm',
'city_mpg','highway_mpg','price'
]
# Filter for lines where power and price are available
df = df[(df.horsepower != '?')]
df = df[(df.price != '?')]
df = df.reset_index()
# Filter for lines where power and price are available
df['horsepower'] = df['horsepower'].astype(int)
df['price'] = df['price'].astype(int)
# Define the last column of the data frame as y and the rest as X
y = df.iloc[:, -1]
X = df.iloc[:, :-1]
return df, X, y
df, X, y = load_auto_data_set()
from sklearn.preprocessing import OneHotEncoder
X_selected = X[["wheel_base", "length", "width","height"]].reset_index()
# define and fit the OneHotEncoder
ohe = OneHotEncoder()
ohe.fit(df[['make']])
# transform the "make" column
make_one_hot_sklearn = pd.DataFrame(ohe.transform(df[["make"]]).toarray(), columns=ohe.categories_[0])
X = X_selected.join(make_one_hot_sklearn)
X = np.array(X)
y = np.array(y)
训练模型
在定义输入和输出变量后,我们尝试用我们的算法进行第一次测试,以查看我们是否可以实际用于预测。
首先,我们将数据集拆分为训练集和测试集。
import tree_algorithms
from sklearn.metrics import mean_squared_error, mean_absolute_error
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
regr = tree_algorithms.RegressionTree(min_samples_split=5, max_depth=20)
regr.fit(X_train, y_train)
y_pred = regr.predict(X_test)
# calculate mean squared error
print(f"Mean Squared Error: {round(mean_squared_error(y_test, y_pred), 1)}")
我们的“从头开始的决策树”的 MSE — 作者截图
与现有的 scikit-learn 库进行比较
为了比较,我们现在尝试使用 scikit-learn 的"DecisionTreeRegressor"库。在这种情况下,我们训练的模型表现完全一样。我不会在这里探讨结果是好是坏。如果你想知道如何调整回归模型并找到表现最佳的模型,你可以在我以前的文章中找到更详细的适用方法解释(这里 或 这里)。
from sklearn.datasets import load_diabetes
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
regr = DecisionTreeRegressor(min_samples_split=5, max_depth=20)
regr.fit(X_train, y_train)
y_pred = regr.predict(X_test)
# calculate mean squared error
print(f"Mean Squared Error: {round(mean_squared_error(y_test, y_pred), 1)}")
scikit-learn 决策树的 MSE — 作者截图
5. 总结
决策树是许多出色算法的基础,如随机森林、XGBoost、LightGBM 和 CatBoost。它们背后的概念非常直观,通常易于理解,至少当你尝试逐一理解各个子概念时。通过这篇文章,你已经通过理解每个树集成算法的核心——决策树,迈出了良好的一步。
我计划发布更多关于使梯度提升框架如此高效的每个概念的文章。
喜欢这个故事吗?
-
如果你喜欢阅读并想了解更多关于机器学习的概念、算法和应用,你可以查看 我所有相关的文章列表。
-
如果你不想错过新文章,你可以 免费订阅 以便在我发布新故事时收到通知。
-
成为 Medium 会员,阅读更多来自其他作者和我的故事。你可以通过使用我的 推荐链接 来支持我。你不会额外花费任何费用,我将获得佣金。
如果你有任何问题,请随时通过 LinkedIn 联系我!
6. 参考文献
Carlens, H. (无日期). 竞争性机器学习的现状. ML 竞赛。检索日期 2023 年 3 月 17 日,来自 https://mlcontests.com/state-of-competitive-machine-learning-2022/archive.ics.uci.edu/ml/datasets/automobile
Chow, R. (2021 年 8 月 31 日). 决策树与随机森林算法:决策驱动因素. 数据科学史。 https://www.historyofdatascience.com/decision-tree-and-random-forest-algorithms-decision-drivers/
Géron, A. (2022). 使用 Scikit-Learn、Keras 和 TensorFlow 的实践机器学习:构建智能系统的概念、工具和技术 (第三版)。 O’Reilly Media, Inc.
Google Developers (导演). (2017 年 9 月 13 日). 从头开始编写决策树分类器——机器学习食谱第 8 集. https://www.youtube.com/watch?v=LDRbO9a6XPU
Swalin, A. (2019 年 6 月 11 日). CatBoost vs. Light GBM vs. XGBoost. Medium. https://towardsdatascience.com/catboost-vs-light-gbm-vs-xgboost-5f93620723db
UCI 机器学习库:汽车数据集. (无日期). 取自 2023 年 2 月 24 日, 来源 https://archive.ics.uci.edu/ml/datasets/automobile
7. 附录
- 从头开始的回归树
import numpy as np
from collections import Counter
from sklearn.metrics import mean_squared_error
from collections import Counter
class Node():
def __init__(
self,
feature=None,
threshold=None,
left=None,
right=None,
value=None
):
self.feature = feature
self.threshold = threshold
self.left = left
self.right = right
self.value = value # is it a leave node?
def is_leaf_node(self):
return self.value is not None
class RegressionTree():
def __init__(
self,
min_samples_split=2,
max_depth=100):
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.root = None
def fit(self, X, y):
self.root = self._grow_tree(X, y)
def _grow_tree(self, X, y, depth=0):
# check the stopping criteria
n_samples, n_feats = X.shape
if (depth>=self.max_depth or n_samples<self.min_samples_split):
leaf_value = np.mean(y)
return Node(value=leaf_value)
feat_idxs = np.random.choice(n_feats, n_feats, replace=False)
# find the best split
best_feature_ixd, best_threshold = self._best_split(X, y, feat_idxs)
# create child nodes
left_idxs, right_idxs = self._split(X[:, best_feature_ixd], best_threshold)
left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth+1)
right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth+1)
return Node(best_feature_ixd, best_threshold, left, right)
def _best_split(self, X, y, feat_idxs):
y_mean = np.mean(y)
residuals_y = (y - y_mean)**2
y_mse = np.mean(residuals_y)
best_feature_ixd, best_threshold = None, None
lowest_mse = y_mse
for feat_idx in feat_idxs:
# define possible thresholds for the split
X_column = X[:, feat_idx]
thresholds = np.convolve(np.sort(X_column), np.ones(2)/2, mode='valid')
for threshold in thresholds:
# getting the left and right nodes
left_idxs, right_idxs = self._split(X_column, threshold)
# calculate the weighted avg. mse of children
n = len(y)
n_l, n_r = len(left_idxs), len(right_idxs)
mse_l = self._squared_error(y[left_idxs])
mse_r = self._squared_error(y[right_idxs])
child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
if lowest_mse > child_mse:
lowest_mse = child_mse
best_feature_ixd = feat_idx
best_threshold = threshold
return best_feature_ixd, best_threshold
def _split(self, X_column, split_thresh):
left_idxs = np.argwhere(X_column <= split_thresh).flatten()
right_idxs = np.argwhere(X_column > split_thresh).flatten()
return left_idxs, right_idxs
def _squared_error(self, y):
# calculate the mean value for all observations
y_mean = np.mean(y)
# calculate the residuals to y_mean
mean_squared_error = np.mean((y - y_mean)**2)
return mean_squared_error
def predict(self, X):
return np.array([self._traverse_tree(x, self.root) for x in X])
def _traverse_tree(self, x, node):
if node.is_leaf_node():
return node.value
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
return self._traverse_tree(x, node.right)
数据合同的另一面:唤醒消费者责任
通过明确数据消费者的承诺来驱动价值创造的路径
·
跟随 发表在 Towards Data Science ·6 min read·Nov 16, 2023
–
由 Midjourney 生成
数据合同中的双重责任平衡
如果一支球队有一半的队员不知道他们需要进球,那确实会是一场混乱和低效的景象。然而,在许多数据组织中,情况是否正是如此?
如今,许多数据组织正在采用数据合同作为增强协作和推动效率的手段。根据我在数据战略咨询公司的经验,我注意到一个反复出现的模式:尽管这些合同通常精心定义了数据提供者的责任 — 从确保数据准确性到维护稳定的架构 — 但它们往往忽视了数据消费者的角色。
就好像一旦数据交付,就默认或假设消费者从中提取价值的责任已经得到了认可一样。
关心业务价值创造的数据领导者和专业人士应更加关注数据消费者的责任。如果消费者不能有效地分析、解释和应用数据,并将其置于业务环境中,那么数据合同就未能充分发挥其潜力。因此,一个真正有效的数据合同还应详细说明消费者的义务,确保他们有能力和准备好按照设想利用数据。
随着我们深入探讨,我们将审视忽视消费者承诺如何降低数据合同的业务影响。更重要的是,我们将探讨将这些承诺纳入其中的实际措施,促进数据项目内的业务价值优先方法。
超越合规 — 构想数据消费者的更广泛角色
在相对稀少的关于数据合同中涉及消费者承诺的文献中,明显强调风险和合规性。描述倾向于概述预防措施和确保合法使用,而不是促进价值创造的积极方法。关键点通常包括:
-
数据使用:严格规定数据的可允许使用方式,以防止误用或错误应用。
-
数据安全:要求消费者在其掌握数据后实施强大的保护措施。
-
法规合规:需要将数据使用与隐私和特定行业法律的复杂网络对齐。
-
数据完整性:维护数据的原始状态,防止未经授权的更改。
-
事件管理:建立违规通知和应对程序,强调反应性立场。
虽然这些义务对于保护和合规至关重要,但它们常常使数据消费者处于被动角色。最近,我与一家公用事业公司的首席数据官进行了交谈,他分享了一个启发性的例子。他们刚刚为用户在其数据平台 Databricks 上直接访问数据集实施了自助服务功能。这一举措在技术上取得了成功,访问量很高。然而,首席数据官表达了一个普遍的担忧:尽管数据访问增加了,但下游产生的价值却没有清晰的可见性。这种情况是一个典型的例子,表明当关注点仅在于提供访问权限而不是如何将这种访问转化为业务影响时,可能会出现断层。
要真正将数据作为战略资产加以利用,我们需要将视野扩展到这些限制性框架之外。下一部分将深入探讨如何将消费者承诺的叙事和结构转变为鼓励更动态和以价值为导向的数据使用。
尊重生产者——承诺创造价值
数据合同中的承诺伴随着一定的开销。对于数据生产者或数据产品负责人来说,这意味着需要不断投资于维护数据管道、确保数据质量和提供必要的基础设施。尽管这些任务至关重要,但它们可能会变得昂贵,不仅在财务上,而且在机会成本上也是如此。现在是时候审视消费者方面,以了解这些承诺如何转化为实际的业务价值——或者未能如此。
想象一个场景,其中一个实习生或在任务之间的员工获得了一个数据产品的访问权限,用于一个未能成功的项目。项目被搁置,但数据产品仍然保持活动状态,维护成本存在且随时可以使用。虽然数据可能在其他举措中得到有效利用,但在这个失败的项目背景下,存在明显的断层:数据产品被维护,产生了成本却没有带来任何价值。
更糟糕的是,当一个项目推进时,数据被用来创建一个新的仪表盘,但发现它没有被业务采纳。仪表盘成了数字纸镇,投入的时间和精力都被浪费了。
即使在数据使用最初确实带来显著业务价值的成功案例中,形势也可能迅速变化,使曾经有价值的项目变得过时。例如,消费者可能利用数据推动某个特定的举措,获得高回报。然而,随着时间的推移,业务需求的变化使得这一举措的价值降低,但维护数据访问的承诺和相关成本仍然存在。数据产品继续按照原合同进行维护,即使它不再服务于最初的高价值目的。
这些情景突显了数据合同需要适应业务的动态特性。它们强调了数据消费者的承诺需要超越访问和使用,涵盖积极追求价值生成、效率和适应性。
含义很明确:数据合同需要确保在消费者方面,不仅要有安全合法使用数据的责任,还要有效使用数据,并在必要时进行调整,以保持与业务价值的一致性。下一部分将探讨如何构建灵活、以价值为中心且能够随着业务需求变化的数据消费者承诺策略。
目标评分 — 从数据产品到业务价值
核心原则很简单:企业内的每一个数据参与都应该带来超过其相关成本的价值。在这个背景下,一个具备良好 SLA、可靠性和一致性可访问性的数据产品就像是足球中的一次优秀传球——它创造了机会,而将其转化为进球则依赖于数据消费者。
在进行业务价值创造评估的过程中,一种由 Kindata 首创的方法使我认识到,将价值生成视为两个不同阶段的优势:
· 业务用例的交付:这涉及到数据消费者制定战略报告、AI/ML 模型或其他分析工具,以提供信息和支持业务决策。
· 有效的业务使用:最终的成功指标在于这些工具如何被业务用来实现财务和非财务目标。这个阶段通常会揭示业务部门在数据计划中的参与程度,这是获得真实业务价值的关键因素。
数据消费者在业务价值创造中处于核心位置。作为直接使用数据产品的人,他们处于‘进球’的位置——即交付业务价值。认识到他们的关键作用也意味着理解他们在使用这些数据资源时需要承担的责任。
为了培养这种责任文化并确保数据消费者不仅仅是参与者,而是积极的价值创造者,你的组织可以采用一种结构化的方法,制定正式的数据消费者承诺:
1. 记录数据使用:记录每一次与数据产品的互动,并明确其目的。在这一阶段的明确性为责任奠定了基础。
2. 衡量结果:定期测量数据产品对业务目标的影响。这不仅包括业务用例的交付,还包括与业务赞助商的持续互动,以确保价值随着时间的推移得到积极实现。
3. 向数据生产者反馈:分享数据产品对业务成果有显著贡献的成功案例,并识别进一步改进的机会。
4. 释放不必要的承诺:作为数据消费者,当你确定某个数据产品不再符合你的目的时,请立即通知数据生产者解除他们的义务。这一关键步骤确保资源被有效重新分配,且不会浪费在未使用的数据产品上的维护工作。
通过贯彻这些实践,数据消费者成为价值创造的积极主体,确保数据产品始终与业务的总体目标一致并为其贡献力量。当然,理解这些承诺只是起点。它们需要通过正确的文化、治理和工具的结合来实施。
数据产品和数据契约是组织数据团队并在消费者承诺上增加额外关注的重要方式,可以产生显著的影响。
就像在足球比赛中,每个团队成员都必须对得分目标保持一致一样,在数据领域,目的的明确性同样至关重要。如果我们团队的重要部分不理解最终目标是为了得分——创造业务价值,那么我们就无法希望取得胜利。
因此,当我们完善我们的战略并明确我们的策略时,让我们不要失去目标。毕竟,在数据驱动的成功之路上,目标至关重要。