精通 Python Asyncio 中的同步原语:全面指南
PYTHON CONCURRENCY
asyncio.Lock、asyncio.Semaphore、asyncio.Event 和 asyncio.Condition 的最佳实践
·发表于 Towards Data Science ·8 分钟阅读·2023 年 5 月 29 日
–
图片来源:由作者创建,Canva
这是我在 Python Concurrency 系列中的一篇文章,如果你觉得有用,可以从这里阅读其余部分。
介绍
在本文中,我将介绍为什么在 Python 的 asyncio 中需要同步原语,以及几种同步原语的最佳实践。在文章的最后部分,我将带你通过一个实际的同步原语示例进行讲解。
为什么你需要 asyncio 中的同步原语
任何使用过 Python 多线程的人都知道多个线程共享相同的内存块。因此,当多个线程同时对相同区域执行非原子操作时,会发生线程安全问题。
由于 asyncio 运行在单线程上,是否没有类似的线程安全问题?答案是否定的。
asyncio 中的并发任务是异步执行的,这意味着可能会有多个任务在时间上交替执行。当一个任务访问特定的内存区域并等待 IO 操作返回时,另一个任务也同时访问该内存区域,就会触发并发错误。
为了避免此类错误,Python asyncio 引入了一种类似于多线程的同步原语功能。
此外,为了避免过多任务同时访问资源,asyncio 的同步原语通过限制同时访问资源的任务数量来提供保护资源的能力。
接下来,让我们看看 asyncio 中有哪些同步原语可供使用。
Python asyncio 的同步原语
锁
在介绍这个 API 之前,让我们看看一种情况:
假设我们有一个并发任务需要一份网站数据。它会首先检查缓存中是否有数据;如果有,它会从缓存中获取数据;如果没有,它会从网站中读取数据。
由于读取网站数据以返回并更新缓存需要一些时间,当多个并发任务同时执行时,它们都假设这些数据不在缓存中,并同时发起远程请求,如下代码所示:
两个任务都认为缓存中没有数据,从而访问远程网站。作者提供的图片
这不符合我们最初的设计意图,因此asyncio.Lock
派上了用场。
当并发任务需要首先获取锁时,我们可以检查缓存中是否有数据,其他未获取锁的任务将等待。直到获取锁的任务完成更新缓存并释放锁,其他任务才能继续执行。
整个流程图如下所示:
作者提供的图片
让我们看看如何编写代码:
只有第一个任务需要更新缓存。作者提供的图片
问题解决了,是不是很简单?
信号量
有时,我们需要访问一个有限并发请求的资源。例如,一个特定的数据库只允许同时打开五个连接。或者根据你拥有的订阅类型,某个 Web API 仅支持一定数量的并发请求。
在这种情况下,你需要使用asyncio.Semaphore
。asyncio.Semaphore
使用一个内部计数器,每次获取 Semaphore 锁时计数器减少 1,直到计数器变为零。
信号量将限制并发任务的数量。作者提供的图片
当asyncio.Semaphore
的计数器为零时,其他需要锁的任务将会等待。在执行其他任务后调用释放方法时,计数器会增加 1,等待的任务可以继续执行。
代码示例如下:
这样,我们可以限制同时访问的连接数量。
有界信号量
有时,由于代码的限制,我们不能使用async with
来管理获取和释放信号量锁,因此我们可能会在某处调用acquire
,在另一处调用release
。
如果我们不小心多次调用asyncio.Semaphore
的release
方法,会发生什么?
如代码所示,我们限制同时运行两个任务,但由于我们调用了多次释放,我们可以在下次同时运行三个任务。
为了解决这个问题,我们可以使用asyncio.BoundedSemaphore
。
从源代码中我们可以知道,当调用release
时,如果计数器的值大于初始化时设置的值,将抛出ValueError
。
当我们多次调用释放方法时,会抛出 ValueError 异常。图片来源于作者
因此,问题正在得到解决。
事件
Event
维护一个内部布尔变量作为标志。asyncio.Event
有三个常用方法:wait
、set
和clear
。
当任务运行到event.wait()
时,任务处于等待状态。此时,可以调用event.set()
将内部标记设置为 True,所有等待的任务可以继续执行。
当任务完成时,需要调用event.clear()
方法将标记的值重置为 False,以将事件恢复到初始状态,并且下次可以继续使用事件。
我将展示如何使用Event
在文章末尾实现一个事件总线,而不是示例代码。
条件
asyncio.Condition
类似于asyncio.Lock
和asyncio.Event
的结合体。
首先,我们将使用async with
确保获取条件锁,然后调用condition.wait()
释放条件锁,使任务暂时等待。当condition.wait()
返回时,我们重新获取条件锁,以确保只有一个任务同时执行。
当一个任务通过condition.wait()
暂时释放锁并进入等待状态时,另一个任务可以通过async with
获取条件锁,并通过condition.notify_all()
方法通知所有等待的任务继续执行。
流程图如下:
asyncio.Condition 的工作流程。图片来源于作者
我们可以通过一段代码演示asyncio.Condition
的效果:
有时,我们需要asyncio.Condition
等待特定事件发生后才能继续执行下一步。我们可以调用condition.wait_for()
方法,并将一个方法作为参数传递。
每次调用condition.notify_all
时,condition.wait_for
会检查参数方法的执行结果,如果结果为 True,则结束等待;如果结果为 False,则继续等待。
我们可以通过一个示例来演示wait_for
的效果。在以下代码中,我们将模拟一个数据库连接。在执行 SQL 语句之前,代码会检查数据库连接是否已经初始化,如果连接初始化完成,则执行查询;否则,等待直到连接初始化完成:
使用同步原语的一些提示
记得在需要时使用超时或取消。
在使用同步原语时,我们通常是在等待特定 IO 操作的完成。然而,由于网络波动或其他未知原因,任务的 IO 操作可能比其他任务花费更长时间。
在这种情况下,我们应该为操作设置一个超时,以便在执行时间过长时,能够释放锁并允许其他任务及时执行。
在另一个情况下,我们可能会循环执行一个任务。它可能会使一些任务在后台等待,阻止程序正确结束。这时,记得使用 cancel 来终止任务的循环执行。
避免使用同步原语或只锁定最少的资源
我们都知道 asyncio 的优势在于任务可以在等待 IO 返回时切换到另一个任务执行。
但一个 asyncio 任务通常同时包含 IO 绑定操作和 CPU 绑定操作。如果我们在任务上锁定了太多代码,它将无法及时切换到另一个任务,从而影响性能。
因此,如果没有必要,尽量不要使用同步原语或仅锁定最少的资源。
为了避免一些其他竞争锁定的情况
asyncio 中没有 RLock,因此在递归代码中不要使用锁。
与多线程一样,asyncio 也有死锁的可能性,因此尽量避免同时使用多个锁。
实践中的高级技术:基于 asyncio 的 Event Bus
在文章前面的介绍之后,我相信你对如何正确使用 asyncio 的同步原语有了清晰的理解。
接下来,我将通过带你实现一个事件总线,教你如何在实际项目中使用同步原语。
像往常一样,作为架构师的第一步是设计 EventBus API。
由于 EventBus
使用字符串进行通信,并且在内部,我打算使用 asyncio.Event
来实现与每个字符串对应的事件,我们将从实现一个 _get_event
方法开始:
on
方法将回调函数绑定到特定事件上:
trigger
方法可以手动触发事件并传入相应的数据:
最后,让我们编写一个 main
方法来测试 EventBus 的效果:
在主方法结束时,记得使用超时来防止程序一直执行,就像我之前警告的那样。
代码按预期执行。图片来自作者
如你所见,代码按预期执行了。是不是很简单?
结论
本文首先介绍了为什么 Python asyncio 需要同步原语。
接着,我介绍了 Lock、Semaphore、Event 和 Condition 的最佳实践,并给出了一些正确使用它们的提示。
最后,我完成了一个关于 asyncio 同步原语的动手训练的小项目,希望能帮助你更好地在实际项目中使用同步原语。
随时评论、分享或与我讨论关于 asyncio 的话题。
你可以通过我的文章列表获取更多关于 Python 并发的知识:
Python 并发
查看列表 10 个故事!用 Aiomultiprocess 超级充实你的 Python Asyncio:全面指南 [## 通过我的推荐链接加入 Medium - Peng Qian
阅读 Peng Qian(以及 Medium 上成千上万其他作者)的每一个故事。你的会员费直接支持 Peng…
qtalen.medium.com](https://qtalen.medium.com/membership?source=post_page-----ae1ae720d0de--------------------------------)
掌握机器学习工作流的艺术:变压器、估算器和管道的全面指南
编写无缝代码以获得最佳结果
·
关注 发布于 Towards Data Science ·14 min read·2023 年 6 月 9 日
–
“只要我现在能理解它,写成这样也没关系,而好的一点是,它确实有效!我成功地用我的模型奇迹般地得到了一个相当不错的结果,真是一个不错的收尾。”
不,我来告诉你,这还不够好。确实,当你开始一个机器学习项目时,许多新手和中级分析师都急于制作出中等水平的模型,却缺乏适当的工作流程。虽然有时候问题本身很简单,但如果不遵循适当的工作流程,常常会导致一些难以察觉的潜在问题,比如数据泄漏。
“只要有效,就足够好了。” 让我告诉你,这并不是。让我们快速模拟一个场景,你需要向高级分析师解释你的工作。这里有一些问题。如果今天有效,是否能保证明天也有效,并且容易重复?你能在包含 200 多个单元格的 Notebook 中解释你的模型工作流程的预处理步骤吗?如果你以这种方式进行交叉验证,是否会暴露测试数据集并使模型性能膨胀?这些问题很棘手,不是吗?
让我告诉你,实际上,你并不孤单,也并没有那么远。即使在参加了多个商业分析和机器学习课程后,我的任何一个讲师都没有分享我下面所要分享的工具和技巧。我会说,这些不是每个人在第一次接触 Scikit-Learn 时都关注的亮点课程。然而,它们会产生一致的结果,显著提升你的代码编写水平。想象一下,轻松处理数据,流畅地转换特征,并训练复杂的模型,同时保持代码的优雅和简洁。这就是我们在本综合指南结束时的目标,希望你能被下面的实践所说服。让我们开始吧。
目录
-
采用流水线的理由
-
估算器
-
变换器
-
流水线
-
自定义估算器
-
特征联合
-
真实世界数据集示例:银行营销与网格搜索交叉验证
采用流水线的理由
1. 精简的工作流程。 利用流水线可以实现数据预处理和建模过程中的多个步骤的无缝集成。它使你能够将各种变换器和估算器串联起来,确保从数据预处理到模型训练和评估的流程清晰、简洁且自动化。通过将你的预处理和建模步骤封装在流水线中,你的代码变得更加有组织、模块化,并且更易于理解。它改善了代码的外观和可维护性,因为每一步都被清楚地定义。将流水线中的每一步视为独立的,你可以在不担心一个预处理步骤如何影响其他步骤的情况下进行更改或添加步骤!
作者提供的图片
2. 防止数据泄露。 这是每个分析师都害怕的对手。数据泄露可能发生在测试数据集的信息无意中影响了预处理步骤或模型训练,从而导致过于乐观的性能估计。从某种程度上来说,你是在泄露关于将要测试的内容的信息,使你的学习模型提前看到将要测试的内容。显然,“他在试图吹嘘”。 通常的经验法则是只拟合训练数据集,然后同时转换训练和测试数据集。下面的代码展示了某些人错误的地方。 此外,你通常会有多个预处理步骤,这些步骤通常涉及变换器,例如 StandardScaler()
、MinMaxScaler()
、OneHotEncoder()
等。想象一下在整个工作流程中多次进行拟合和转换过程,难道这不会让人困惑和不便吗?
#Variation 1: Do not fit transform both training and testing dataset!
ss = StandardScaler()
X_train_scaled = ss.fit_transform(X_train)
X_test_scaled = ss.fit_transform(X_test)
#Variation 2: Remember to transform your training dataset!
X_train_scaled = ss.fit(X_train)
X_test_scaled = ss.transform(X_test)
3. 超参数调整和交叉验证。 使用如 GridSearchCV 之类的技术轻松调整管道中所有步骤的超参数。然而,这一步骤中的错误往往被忽视。让我们看一个简单的例子。
#Previously an oversight, correction contributed by @Scott Lyden
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
#Without Pipeline
select = SequentialFeatureSelector(RandomForestClassifier(n_estimators=100, n_jobs=-1),
n_features_to_select=8,
direction='forward').fit(X,y)
X_selected = select.transform(X)
logreg = LogisticRegression()
np.mean(cross_val_score(estimator=logreg,
X=X_selected,
y=y,
n_jobs=-1))
#With Pipeline
pipe = Pipeline([("select", SequentialFeatureSelector(RandomForestClassifier(n_estimators=100, n_jobs=-1),
n_features_to_select=8,
direction='forward',
n_jobs=-1)),
("log", LogisticRegression())])
np.mean(cross_val_score(estimator=pipe, X=X, y=y))
尝试运行这两个示例:虽然交叉验证分数相差不远,但没有使用 Pipeline 的部分泄露了信息,因为特征选择步骤是在整个数据集上进行的。当我们到达交叉验证步骤时,数据集被分为训练集和验证集,它们本质上来自同一来源(训练集在进行特征选择时已从验证集中学习到信息)。如果你发现这一部分难以理解,请尝试重新阅读这一段并自己编码以加深理解。
估算器
在我们深入了解 Pipeline 能做什么之前,让我们暂时离开,了解组成 Pipeline 的组件——估算器。我们将在下一节中讨论其他组件——变换器、预测器和模型。
许多人在使用 Scikit-learn 时常常对估算器这个术语感到困惑。人们倾向于将估算器与预测能力联系起来——也就是特别指 predict
方法。虽然这种说法有一定的真实性,但遗憾的是,最多只是部分真实。估算器基本上是 Scikit-learn 库的构建块。估算器是一种工具,它可以从你的训练集学习,创建一个可以对新数据进行预测或推断的模型。由于所有估算器都有 fit
方法来从训练集学习,它们继承自 BaseEstimator
。
从BaseEstimator
本身来看,没有predict
方法,只有fit
。一个估计器并不一定需要有predict
方法,虽然有些有。一个具有predict
方法的估计器试图基于学习到的模型对新的、未见过的数据进行预测。例如,像线性回归、随机森林分类器、梯度提升分类器等回归器和分类器都是具有predict
方法的估计器。
更进一步,我们来看看LogisticRegression
类的原始文档²。在下面的代码片段中,我们观察到该类继承了BaseEstimator
以获得fit
方法,并继承了LinearClassifierMixin
以获得predict
方法。
Scikit-learn GitHub(BSD-3)
变换器
变换器是一种具有transform
方法的估计器。请注意,这里的“变换器”特指 Scikit-learn 上下文。它不应与近年来备受关注的神经网络架构中的变换器混淆。
简而言之,变换器的作用是以某种方式转换/处理预测变量(X
),使其可以被机器学习算法使用。这可能是使用像StandardScaler
和MinMaxScaler
这样的显著工具对连续预测变量进行缩放,或者使用OneHotEncoder
或OrdinalEncoder
对分类预测变量进行编码。
更进一步,变换器具有 fit-transform 机制,其中它使用fit
方法从训练数据中学习,然后使用transform
方法将学习到的转换应用于训练数据和测试数据。这确保了整个过程中的转换一致应用。
再进一步,为了遵循 Scikit-learn API 实现规则,变换器通常从BaseEstimator
继承其fit
方法,从TransformerMixin
继承其transform
方法。我们来看看StandardScaler
库的原始文档³。
ColumnTransformer⁵
有时,你可能需要根据需求仅对某些列应用特定的变换。例如,对没有特定层次结构的分类特征应用OneHotEncoder
,对具有特定层次结构和排序的分类特征(即 T 恤尺寸,我们通常有 XS<S<M<L<XL 的排序)应用OrdinalEncoder
。我们可以使用ColumnTransformer
来实现这种分离。
from sklearn.compose import ColumnTransformer
ohe_categorical_features = ['a', 'b', 'c']
ohe_categorical_transformer = Pipeline(steps=[
('ohe', OneHotEncoder(handle_unknown='ignore', sparse_output=False, drop='first'))
])
orde_categorical_features = ['d', 'e', 'f']
orde_categorical_transformer = Pipeline(steps=[
('orde', OrdinalEncoder(dtype='float'))
])
col_trans = ColumnTransformer(
transformers=[
('ohe_categorical_features', ohe_categorical_transformer, ohe_categorical_features),
('orde_categorical_features', orde_categorical_transformer, orde_categorical_features),
],
remainder='passthrough',
n_jobs=-1,
)
正如你可能预料的那样,我们将把变量col_trans
作为代码后续大整体管道的一部分放在上面。简单而优雅。
管道
Pipeline⁶
类以顺序方式执行管道中的估算器,将一个步骤的输出作为下一个步骤的输入。这本质上实现了链式操作的概念。根据Scikit-learn 文档⁴的说明,以下是估算器有资格作为管道的一部分的标准。
要使估算器能够与
pipeline.Pipeline
一起使用,除了最后一步之外,需要提供fit
或fit_transform
函数。为了能够在训练集之外的数据上评估管道,它还需要提供transform
函数。管道中的最后一步没有特别要求,只需具有fit
函数。
使用Pipeline
,我们去除了在每个估算器和/或转换器上调用fit
和transform
方法的冗余步骤。直接从管道调用一次fit
方法就足够了。其背后的工作原理是,首先在第一个估算器上调用fit
,然后transform
输入并传递给下一个估算器。实际上,管道的效果取决于最后一个估算器(它包含了管道中最后一个估算器的所有方法)。如果最后一个估算器是回归器,那么管道也可以作为回归器使用。如果最后一个估算器是转换器,管道也是如此。
以下是如何使用Pipeline
类的示例。
imputer = KNNImputer(n_neighbors=5)
feature_select = SequentialFeatureSelector(RandomForestClassifier(n_estimators=100), n_features_to_select=8, direction='forward')
log_reg = LogisticRegression()
pipe = Pipeline([("imputer", imputer),
("select", feature_select),
("log", log_reg)])
简而言之,Pipeline
的参数是一个顺序执行的元组列表。元组的第一个元素是你任意设定的名称,用来标识估算器,有点像 ID。而第二个元素是估算器对象。简单吧?如果你不擅长起名字,Scikit-learn 提供了简写的make_pipeline
方法,省去了起名字的麻烦。
from sklearn.pipeline import make_pipeline
imputer = KNNImputer(n_neighbors=5)
feature_select = SequentialFeatureSelector(RandomForestClassifier(n_estimators=100), n_features_to_select=8, direction='forward')
log_reg = LogisticRegression()
make_pipeline(imputer, feature_select, log_reg)
自定义估算器
到目前为止,像StandardScaler
和MinMaxScaler
这样的方法看起来很好,并且适用于许多情况。问题是,如果你有自己定制的方法来处理和预处理数据集,可以将其整洁地整合到Pipeline
类中吗?答案是肯定的!有两种方法可以实现这一点——利用FunctionTransformer
或编写你自己的自定义类。
比如你想对数据集的一部分进行 Box-Cox 变换。
from scipy.stats import boxcox
from sklearn.preprocessing import FunctionTransformer
from sklearn.compose import ColumnTransformer
boxcox_features = ['x1', 'x2']
boxcox_transformer = Pipeline(steps=[
('boxcox', FunctionTransformer(lambda x: boxcox(x)[0])
])
col_trans = ColumnTransformer(
transformers=[
('boxcox_features', boxcox_transformer, boxcox_features),
...
],
remainder='passthrough',
n_jobs=-1,
)
第二种方法是编写一个自定义类,继承自BaseEstimator
和TransformerMixin
,如果你编写的是转换器估算器。例如,如果你编写一个分类任务的估算器,那么应继承自ClassifierMixin
。
比如你想编写一个移除异常值的类,并将其整合到你的管道中。
def outlier_thresholds(df: pd.DataFrame,
col: str,
q1: float = 0.05,
q3: float = 0.95):
#1.5 as multiplier is a rule of thumb. Generally, the higher the multiplier,
#the outlier threshold is set farther from the third quartile, allowing fewer data points to be classified as outliers
return (df[col].quantile(q1) - 1.5 * (df[col].quantile(q3) - df[col].quantile(q1)),
df[col].quantile(q3) + 1.5 * (df[col].quantile(q3) - df[col].quantile(q1)))
def delete_potential_outlier_list(df: pd.DataFrame,
cols: list) -> pd.DataFrame:
for item in cols:
low, high = outlier_thresholds(df, col)
df.loc[(df[col]>high) | (df[col]<low),col] = np.nan
return df
class OutlierRemove(BaseEstimator, TransformerMixin):
def __init__(self, outlierlist):
self.outlierlist = outlierlist
def fit(self, X, y=None):
return self
def transform(self,X,y=None):
return delete_potential_outlier_list(X, self.outlierlist)
我特别希望将您的注意力集中在OutlierRemove
类上。在这里,我们有fit
方法返回self
以便继续链式调用,还有transform
方法进行异常值的删除。之后,我们可以将该类简单地合并到我们的Pipeline
中,如下所示。
pipe = Pipeline([("remove_outlier", OutlierRemove(["a", "b", "c"])),
("imputer", imputer),
("select", feature_select),
("log", log_reg)])
FeatureUnion
这里是令人困惑的部分——FeatureUnion
的作用与Pipeline
相同,但它们的工作方式却大相径庭。在FeatureUnion
中,fit
和transform
方法不是一个接一个地执行。每个转换器估算器独立地fit
数据,然后并行地应用transform
方法。最终结果被组合在一起。想象一下下面的代码。在这里,我们可以使用FeatureUnion⁷
并行运行数值和分类预测的预处理,因为它们相互独立。这带来了更快和更高效的操作。
from sklearn.pipeline import FeatureUnion
standard_numerical_features = ['x1', 'x2']
standard_numerical_transformer = Pipeline(steps=[
('remove_outlier', OutlierTrans(standard_numerical_features)),
('scale', StandardScaler())
])
ohe_categorical_features = ['x3', 'x4']
ohe_categorical_transformer = Pipeline(steps=[
('ohe', OneHotEncoder(handle_unknown='ignore', sparse_output=False, drop='first'))
])
feature_union = FeatureUnion(
transformers=[
('standard_numerical_features', standard_numerical_transformer),
('ohe_categorical_features', ohe_categorical_transformer),
],
n_jobs=-1,
)
pipeline = Pipeline([
('feature_union', feature_union),
('model', RandomForestClassifier())
])
pipeline.fit(X_train, y_train)
真实世界数据集示例:银行营销与网格搜索 CV
在这里,我希望通过使用受葡萄牙金融机构启发的真实数据集来说明上述内容。该数据集可以在UCI 机器学习库¹上获取,供公众使用并引用。
请允许我跳过所有的探索性数据分析和可视化,直接进入管道建模部分。
1. 导入数据集
import pandas as pd
df = (pd
.read_csv('../../dataset/bank_marketing/bank-11k.csv', sep=',')
.rename(columns={'y': 'deposit'})
.pipe(lambda df_: df_.assign(deposit=np.where(df_.deposit == "no", 0, 1)))
)
简而言之,上面的代码实现了以下几个功能:
-
使用逗号分隔符导入数据集
-
将列‘y’重命名为‘deposit’
-
将列中的“no”和“yes”编码为 0 和 1
2. 训练-测试拆分
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(df.drop(columns=['deposit']),
df[['deposit']].values.ravel(),
test_size=0.2,
random_state=42)
3. 编写另外 3 个自定义类
from sklearn.base import BaseEstimator, TransformerMixin
#Custom class #1: switch between classifiers
class ClfSwitcher(BaseEstimator):
#By default, run XGBClassifier
def __init__(self, estimator = XGBClassifier()):
self.estimator = estimator
def fit(self, X, y=None, **kwargs):
self.estimator.fit(X, y)
return self
def predict(self, X, y=None):
return self.estimator.predict(X)
def predict_proba(self, X):
return self.estimator.predict_proba(X)
def score(self, X, y):
return self.estimator.score(X, y)
#Custom class 2: remove outliers
def outlier_thresholds(df: pd.DataFrame,
col: str,
q1: float = 0.05,
q3: float = 0.95):
return (df[col].quantile(q1) - 1.5 * (df[col].quantile(q3) - df[col].quantile(q1)),
df[col].quantile(q3) + 1.5 * (df[col].quantile(q3) - df[col].quantile(q1)))
def delete_potential_outlier_list(df: pd.DataFrame,
cols: list) -> pd.DataFrame:
for item in cols:
low, high = outlier_thresholds(df, col)
df.loc[(df[col]>high) | (df[col]<low),col] = np.nan
return df
class OutlierTrans(BaseEstimator, TransformerMixin):
def __init__(self, outlierlist):
self.outlierlist = outlierlist
def fit(self, X, y=None):
return self
def transform(self,X,y=None):
return delete_potential_outlier_list(X, self.outlierlist)
#Custom class #3: add new columns, drop column, and modify data types
class TweakBankMarketing(BaseEstimator, TransformerMixin):
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
return (X
.assign(pdays_cat=lambda df_: np.where(df_.pdays < 0, "no contact", "contacted"),
previous_cat=lambda df_: np.where(df_.previous == 0, "no contact", "contacted"),
job=lambda df_: np.where(df_.job == "unknown", np.nan, df_.job),
education=lambda df_: np.where(df_.education == "unknown", np.nan, df_.education),
contact=lambda df_:np.where(df_.contact == "unknown", np.nan, df_.contact),
poutcome=lambda df_: np.where(df_.poutcome == "other", np.nan, df_.contact),
) #add new predictors
.drop(columns=['duration']) #drop predictor due to data leakage
.astype({'age': 'int8',
'balance': 'int32',
'day': 'category',
'campaign': 'int8',
'pdays': 'int16',
'previous': 'int16',})
.pipe(lambda df_: df_.astype({column: 'category' for column in (df_.select_dtypes("object").columns.tolist())})) #convert data type from object to category
)
简而言之,上面的代码实现了以下几个功能:
-
类
ClfSwitcher
继承自BaseEstimator
。此类的目的是方便地在分类器之间切换。我们将默认分类器设置为 XGBoost 分类器。 -
方法
outlier_thresholds
和delete_potential_outlier_list
识别每列中的异常值并将其设置为NaN
。类OutlierTrans
是一个继承自BaseEstimator
和TransformerMixin
的转换器。transform
方法返回之前提到的两个方法。 -
类
TweakBankMarketing
是一个自定义类,用于执行自定义转换,例如创建新列、删除不需要的列以及相应地更改数据类型。
4. 准备管道
from sklearn.preprocessing import StandardScaler, MinMaxScaler, OrdinalEncoder, OneHotEncoder,
from sklearn.impute import KNNImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
standard_numerical_features = ['age', 'campaign', 'pdays', 'previous'] #drop pdays
standard_numerical_transformer = Pipeline(steps=[
('remove_outlier', OutlierTrans(standard_numerical_features)),
('scale', StandardScaler())
])
minmax_numerical_features = ['balance']
minmax_numerical_transformer = Pipeline(steps=[
('remove_outlier', OutlierTrans(minmax_numerical_features)),
('scale', MinMaxScaler())
])
ohe_categorical_features = ['job', 'marital', 'default', 'housing', 'loan', 'contact', 'poutcome', 'pdays_cat', 'previous_cat']
ohe_categorical_transformer = Pipeline(steps=[
('ohe', OneHotEncoder(handle_unknown='ignore', sparse_output=False, drop='first'))
])
orde_categorical_features = ['education', 'day', 'month']
orde_categorical_transformer = Pipeline(steps=[
('orde', OrdinalEncoder(dtype='float'))
])
col_trans = ColumnTransformer(
transformers=[
('standard_numerical_features', standard_numerical_transformer, standard_numerical_features),
('minmax_numerical_features', minmax_numerical_transformer, minmax_numerical_features),
('ohe_categorical_features', ohe_categorical_transformer, ohe_categorical_features),
('orde_categorical_features', orde_categorical_transformer, orde_categorical_features),
],
remainder='passthrough',
verbose=0,
verbose_feature_names_out=False,
n_jobs=-1,)
pipeline = Pipeline(steps = [
('tweak_bank_marketing', TweakBankMarketing()),
('col_trans', col_trans),
('imputer', KNNImputer(n_neighbors=5)),
('clf', ClfSwitcher()),
])
pipeline
简而言之,上面的代码实现了以下几个功能:
-
使用
StandardScaler
和MinMaxScaler
对数值列进行缩放 -
使用
OneHotEncoder
和OrdinalEncoder
对分类列进行编码 -
使用
ColumnTransformer
对数据集中的不同列分别进行转换。 -
最终,
Pipeline
无缝地封装了所有内容。
在这个阶段,这是我们构建的管道。
作者图片
5. 为网格搜索 CV 定义超参数
#We define all the hyperparameters for 4 classifiers so that we can easily switch from one to another
params_grid = [
{'clf__estimator': [SGDClassifier()],
'clf__estimator__penalty': ('l2', 'elasticnet', 'l1'),
'clf__estimator__max_iter': [500],
'clf__estimator__tol': [1e-4],
'clf__estimator__loss': ['hinge', 'log_loss', 'modified_huber'],
},
{'clf__estimator': [LogisticRegression()],
'clf__estimator__C': [0.01, 0.1, 1, 10, 100],
'clf__estimator__max_iter': [1000]
},
{'clf__estimator': [RandomForestClassifier(n_estimators=100)],
'clf__estimator__max_features': [3,4,5,6,7],
'clf__estimator__max_depth': [3,4,5]
},
{'clf__estimator': [XGBClassifier()],
'clf__estimator__max_depth': [4,5,6],
'clf__estimator__learning_rate': [0.01, 0.1],
'clf__estimator__n_estimators': [80, 100],
'clf__estimator__booster': ['gbtree'],
'clf__estimator__gamma': [7, 25, 100],
'clf__estimator__subsample': [0.3, 0.6],
'clf__estimator__colsample_bytree': [0.5, 0.7],
'clf__estimator__colsample_bylevel': [0.5, 0.7],
'clf__estimator__eval_metric': ['auc']
},
]
简而言之,上述代码执行的操作如下:
- 为 4 种不同的分类器定义参数网格,即
SGDClassifier
,LogisticRegression
,RandomForestClassifier
,XGBClassifier
。
6. 执行网格搜索 CV
from sklearn.model_selection import GridSearchCV
%%time
grid = GridSearchCV(pipeline, params_grid, cv=5, n_jobs=-1, return_train_score=False, verbose=0)
grid.fit(X_train, y_train)
简而言之,上述代码执行的操作如下:
- 将我们的管道对象作为
GridSearchCV
参数的第一个参数。
7. 打印最佳估计器
print(f'Best params: {grid.best_params_}')
print(f'Best CV score: {grid.best_score_}')
print(f'Validation-set score: {grid.score(X_test, y_test)}')
print(f'Accuracy score: {accuracy_score(y_test, grid.predict(X_test))}')
print(f'Precision score: {precision_score(y_test, grid.predict(X_test))}')
print(f'Recall score: {recall_score(y_test, grid.predict(X_test))}')
print(f'ROC-AUC score: {roc_auc_score(y_test, grid.predict(X_test))}')
在这里,我们获得了 0.74 的验证分数,以及 0.74 的 AUC 分数。
8. 绘制 ROC-AUC 曲线
fpr, tpr, thresholds = skmet.roc_curve(y_test, grid.predict(X_test))
roc_auc = skmet.auc(fpr, tpr)
display = skmet.RocCurveDisplay(fpr=fpr,
tpr=tpr,
roc_auc=roc_auc,
estimator_name='XGBoost Classifier')
display.plot();
图片来源:作者
后记
就是这样!使用估计器和转换器的管道。下次当你处理 ML 项目时,考虑使用这个技术。起初可能觉得难以采用,但持续练习,很快你就能创建出稳健而高效的机器学习管道。
如果你从这篇文章中获得了有用的信息,请考虑在 Medium 上给我一个关注。简单,每周一篇文章,保持更新并走在前沿!
关注我!
参考文献
-
银行营销数据集 [Moro et al., 2014] S. Moro, P. Cortez, 和 P. Rita。基于数据的方法来预测银行电话营销的成功。决策支持系统,Elsevier,62:22–31,2014 年 6 月:
archive.ics.uci.edu/ml/datasets/Bank+Marketing
(CC BY 4.0) -
Scikit-learn 线性模型逻辑回归:
github.com/scikit-learn/scikit-learn/blob/364c77e047ca08a95862becf40a04fe9d4cd2c98/sklearn/linear_model/_logistic.py
-
Scikit-learn 预处理:
github.com/scikit-learn/scikit-learn/blob/364c77e04/sklearn/preprocessing/_data.py#L644
-
开发 Scikit-learn 估计器:
scikit-learn.org/stable/developers/develop.html
-
Scikit-learn ColumnTransformer:
scikit-learn.org/stable/modules/generated/sklearn.compose.ColumnTransformer.html
-
Scikit-learn 管道:
scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html
-
Scikit-learn FeatureUnion:
scikit-learn.org/stable/modules/generated/sklearn.pipeline.FeatureUnion.html
掌握定价优化的艺术 — 一种数据科学解决方案
解锁零售定价优化中的真实世界数据科学解决方案的秘密
·
关注 发表在 Towards Data Science ·16 min 阅读·2023 年 8 月 28 日
–
作者提供的图片
目录:
1. 概述2. 弹性建模3. 优化
1. 概述
定价在商业世界中扮演着至关重要的角色。平衡销售和利润对于任何业务的成功至关重要。我们如何以数据科学的方式来实现这一目标?在本节中,我们将建立一个有效的数据科学解决方案的直觉,然后深入了解每个组件的细节和代码。
注意 — 尽管有不同类型的定价策略,但在本文中,我们将重点关注为拥有足够价格变动历史数据的传统业务/成熟品牌建立定价策略。在详细讨论之前,让我们先看看我们尝试遵循的基本方法 —
图片由作者提供
我们已经为商品 1 绘制了销售和价格图。在过去的 9 个月里,价格发生了 2 次变动,我们可以清楚地看到销售的影响。当价格较低时,销售额较高。现在的问题是如何量化过去价格变动对销售的影响,并预测未来商品的最佳价格。
从 1 月至 4 月的一个有趣观察是,价格固定在$5,但我们仍然观察到销售波动。这是非常正常的,因为在实际世界中,有许多外部因素会影响销售,如季节性、节假日、促销活动、市场营销支出等。因此,我们不对实际销售进行建模,而是使用不同模型推导出的基线销售。
图片由作者提供
你可以观察到,我们正在查看基线销售系列中的平滑销售趋势。它是 100%准确的吗?当然不是!数据科学的关键在于我们能接近现实的程度。现在让我们进入过程 —
假设我们被聘请并要求为 Retailmart 集团的数千种商品提供价格。这些商品在不同商店的价格可能不同。公司提供了过去 5 年的数据。我们应该采用什么方法来解决这个问题?
让我们通过一个价格计量器的例子来理解这一点。假设我们有一个价格计量器,我们已经固定了最低和最高值,并且拨盘可以在这两个极端之间移动。目前,拨盘指向当前价格。我们的目标是将拨盘停在一个可以最大化利润的点上。
现在,当我们将调整拨盘向右移动(即我们在提高价格)时,销售将开始下降,利润率将增加,但
-
我们可以量化这种下降吗?是的,我们可以,这被称为项目的价格弹性。简单来说,价格弹性表示价格变动 1%时,销售额的百分比变化。
-
在现实世界中,销售往往受到促销活动、假期、额外折扣等因素的驱动,但为了优化价格,我们需要排除所有这些外部因素的影响,并计算基线销售。
-
一旦我们量化了销售变化与价格变化的关系,我们需要的答案是,我应该在哪儿停下调整? 为此,我们需要一个目标,通常是最大化利润。利润 = 销售额 * 利润率,因此我们需要在利润值达到最大的位置停下。从数学上讲,这是一种非线性优化的概念,其中值可以在范围内移动。
-
商业规则很重要,我们必须确保最终推荐的价格符合这些规则。
所以这些是我们将遵循的主要步骤,以为给定商店中的每件商品确定正确的价格。让我们更详细地了解这些步骤 —
1. 基线销售/基本单位
这一步是后续步骤的前提步骤。如前所述,我们希望建模价格变化对销售的影响。理想情况下,销售只会受到价格变化的影响,但在实际情况下,情况从未如此 —
所以我们希望模拟我们理想情况下的销售,并通过下面的方程式使用时间序列模型来实现 —
销售 ~ 函数[基线销售 + (促销效果) + (假期效果) + (其他任何效果)]
请注意,有时我们没有实际的数据来反映影响销售的外部因素。在这种情况下,我们可以使用虚拟变量来考虑所有这些因素。例如,如果在某个月我们看到销售突然增加但价格保持不变,我们可以引入一个简单的虚拟变量,在该月标记为 1,其余月份标记为 0。
2. 价格弹性
价格弹性是指销售量相对于某商品价格变化的百分比变化。
举个例子,考虑两种产品牛奶和 ABC 绿茶。你认为哪一个会有较高的价格弹性?
牛奶作为一种必需的日常商品,竞争激烈,显示出较高的价格弹性。即使是价格的微小变化也能显著影响销售,因为其需求广泛。另一方面,ABC 绿茶可能仅在少数商店有售,价格弹性较低。由于其市场定位,小幅度的价格变化不太可能对销售产生重大影响。
我们将如何建模这个?
基线销售 ~ 函数[(价格) + 趋势]
价格变量的系数将用作价格弹性。趋势变量用于考虑由于长期趋势而导致的销售增长,而不一定是由于价格变化。我们将在下面的价格弹性部分详细讨论如何计算弹性。
3. 在范围内的非线性优化
在这一步中,我们将得到旋钮应该停在哪儿的答案。
我们首先定义我们的目标函数——最大化利润
然后我们定义价格计量器的起点和终点,这定义了价格的下界(LB)和上界(UB)
我们已经计算了基线销售和价格弹性,这些量化了销售对价格的敏感性。我们将把所有这些输入放入我们的非线性优化函数中,得到优化后的价格。
用非常简单的话来说,算法将在范围内尝试不同的价格点,并检查目标函数的值,在我们的案例中是利润。它会返回到能够为我们的目标函数获得最大值的价格点。(在线性优化中,可以想象一下梯度下降的工作方式)。我们将在下面的优化部分中讨论计算优化价格的更多细节。
4. 商业规则
那么我们可以直接在我们的商店里实施优化后的价格吗?
不能,但现在还剩下什么?遵循商业规则是任何业务最重要的要求之一。
但我们讨论的定价规则是什么——
-
结束数字规则——通常我们会将产品定价为$999 或$995,而不是$1000。这样做有几个心理学原因,因此我们需要确保最终推荐的价格遵循这些规则(如适用)。
-
产品差距规则——你能否以单个包装的 Maggi 价格高于 4 包装的 Maggi?不能,对吧。通常情况下,如果包装的数量增加,每单位的成本应该下降,或至少保持不变。
这些是业务希望应用的一些商业规则示例。我们将对优化价格进行一些后处理步骤,以获得最终推荐价格。
现在你了解了整体过程,是时候深入探讨更多细节和编码了。
2. 弹性建模
在本节中,我们将了解如何利用这个概念来推导出在多个商店中 1000 多件商品的优化价格。假设我们需要确定过去 3 年在加州商店销售的零食 Yochips 的价格弹性。让我们首先看看价格弹性的定义:
价格弹性定义为价格变化 1%时,销售额的百分比变化。
现在你可能在想,我可以使用哪个算法来计算像 Yochips 这样的商品的价格弹性?
让我们深入探讨经济学书中的常数价格弹性模型,看看是否能将其与一些数据科学算法相关联。
需求函数的乘法形式将是:-
Yi = α*Xi^β(其中 y 为销售/需求,x 为价格,β 为价格弹性)
对两边取对数
log(Yᵢ) = log(α*Xᵢ^β)
log(Yᵢ) = log(α) + β*log(Xᵢ) ……….Eq(1)
log(α) 可以视为截距,类似于 β₀
log(Yᵢ) = β₀ + β₁*log(Xᵢ) ………….Eq(2)
现在对两边进行微分,我们将得到
δY/Y = β₁*δX/X
左侧的项表示 Y 的百分比变化,即销售额的百分比变化,而右侧的项表示价格的百分比变化。现在当
%价格变化 = 1%;则 δX/X = 1
δY/Y = β₁
这意味着销售额的百分比变化将是β₁,这就是我们的弹性。
现在,如果你注意到的话,方程 2 是一个回归方程,其中对数(销售)对数(价格)进行回归,对数(价格)的系数将是我们的价格弹性。
太棒了!现在我们知道计算弹性就像训练一个回归模型一样简单。
但还有一个问题。需求函数方程有一些假设,其中之一是销售仅受价格影响,但在现实世界中通常并非如此,因为销售通常受促销、假期、活动等多个因素的影响。那么解决方案是什么?我们需要计算销售组件,从而去除所有这些额外事件的影响。
有一点需要澄清的是,基础销售指的是单位销售,而不是美元销售。因此,在方程 2 中,我们需要将价格对基础单位进行回归,而不是实际单位销售。那么问题是我们如何从实际销售单位中推导出基础单位?
我们通过一个例子来理解。下面你可以看到销售单位和销售价格的时间序列的每周图:
作者提供的图片
你能在上面的图中看到任何模式吗?这很难判断,因为销售单位系列中有太多波动。这些波动可能由多个因素造成,如假期、促销、活动、FIFA 世界杯等。为了隔离价格变化的影响,我们需要计算排除这些额外因素影响的销售数据。
使用 prophet 模型,我们可以分解时间序列并提取代表基础销售的趋势组件。通过应用这一技术,我们将价格变化的长期影响与其他短期影响分开。让我们看看我们在说什么:
作者提供的图片
在上面的图中,我们将原始对数销售单位(灰色)分解为对数基础单位(黄色线图)
这里有一个代码,你可以用它来分解时间序列并获取将成为基础销售的趋势组件:
# Defining the inputs
timestamp_var = "week_ending_sunday"
baseline_dep_var = "ln_sales"
changepoint_prior_scale_value = 0.3
list_ind_vars_baseline = ['event_type_1_Cultural','event_type_1_National','event_type_1_Religious','event_type_1_Sporting']
# Preparing the datasecloset
df_item_store = df_item_store.rename(columns={timestamp_var: 'ds', baseline_dep_var: 'y'})
df_item_store['ds'] = pd.to_datetime(df_item_store['ds'])
# Initializing and fitting the model
model = Prophet(changepoint_prior_scale= changepoint_prior_scale_value) #Default changepoint_prior_scale = 0.05
# Add the regressor variables to the model
for regressor in list_ind_vars_baseline:
model.add_regressor(regressor)
model.fit(df_item_store)
# Since we are only decomposing current time series, we will use same data is forecasting that was used for modelling
# Making predictions and extracting the level component
forecast = model.predict(df_item_store)
level_component = forecast['trend']
以下是我们需要定义的输入:
-
changepoint_prior_scale_value — 这控制趋势的平滑性。你可以在 prophet 模型文档中阅读更多关于它的信息。
-
list_ind_vars_baseline — 这些包括所有影响销售的额外事件,如节日、体育赛事、文化活动等。
changepoint_prior_scale_value 如何影响趋势:当值较小时,它会导致几乎是一条直线;当值较高时,趋势就不那么平滑了。
代码很简单,首先,我们将“ln_sales”变量重命名为“y”,将“week”变量重命名为“ds”,以满足使用 Prophet 模型的前提条件。接下来,我们初始化 Prophet 模型,指定“changepoint_prior_scale”参数。随后,我们将额外的事件和假期变量纳入模型。最后,我们使用训练模型的同一数据集生成预测,并提取趋势成分。
很好。我们现在有了基础单位系列,我们可以在基础单位(因为我们分解了 log_base_units 系列,已经是对数尺度)和 log(price) 之间拟合线性回归模型。下面是方程:-
log(base units) = 截距 + 弹性*log(price)
使用上述方程,我们可以计算弹性值。在实际操作中,并不是所有的系列都适合建模,因此你可能会遇到一些不同于预期的弹性值。那解决方案是什么呢?如果我们能以某种方式在弹性值上施加约束进行回归该怎么办?答案是使用优化函数。
对于任何优化,基本要求如下:-
-
目标函数 —— 这是我们尝试最小化/最大化的方程。在我们的案例中,它将是我们在线性回归中使用的损失函数 MSE (pred-actual => [截距 + 弹性 *ln_price — 实际值]²)
-
我们尝试优化的参数的初始值,在我们的案例中是截距和弹性。这些初始值可以是任何随机值。
-
参数的边界,即截距和弹性的最小值和最大值
-
优化算法 —— 这取决于库,但你可以使用默认设置,这应该能给你正确的结果
现在让我们来看一下代码:-
# Preparing the matrix to feed into optimization algorithm
x = df_item_store_model
x["intercept"] = 1
x = x[["intercept","ln_sell_price","ln_base_sales"]].values.T
# x_t = x.T
actuals = x[2]
from scipy.optimize import minimize
# Define the objective function to be minimized
def objective(x0):
return sum(((x[0]*x0[0] + x[1]*x0[1]) - actuals)**2) # (intercept*1 + elasticity*(ln_sell_price) -ln_base_sales)²
# Define the initial guess
x0 = [1, -1]
# Define the bounds for the variables
bounds = ((None, None), (-3,-0.5))
# Use the SLSQP optimization algorithm to minimize the objective function
result = minimize(objective, x0, bounds=bounds, method='L-BFGS-B')
# Print the optimization result
print(result)
# Saving the price elastitcity of an item in the dataframe
price_elasticity = result.x[1]
df_item_store_model["price_elasticity"] = result.x[1]
注意,我们为截距定义了初始参数值为 1,为弹性定义了初始参数值为 -1\。截距没有定义边界,而弹性则定义了 (-3,-0.5) 的边界。这就是我们通过优化函数进行回归的主要原因。在运行优化后,我们保存了优化后的价格弹性参数值。太棒了!我们已经计算出价格弹性!
因此,我们在加利福尼亚商店的 Yochips 价格弹性为 -1.28。
让我们看看其他系列的价格弹性:-
低价格弹性:价格上涨时销售额没有显著变化。下面是一个价格弹性为 -0.5 的项目的图表
作者提供的图片
中等价格弹性:价格上涨时销售额有适度下降。下面是一个价格弹性为 -1.28 的项目的图表
作者提供的图片
高价格弹性:价格上涨时销售额有很大下降。下面是一个价格弹性为 -2.5 的项目的图表
作者提供的图片
使用相同的方法,我们可以计算所有物品的价格弹性。在下一篇文章中,我们将探讨如何利用这些弹性值来确定每个物品的优化价格。
3. 优化
在前一部分中,我们已经确定了 Yochips 在加州商店的价格弹性。但这对商店经理并没有帮助,他想知道如何调整 Yochips 的价格以最大化收益。在这篇文章中,我们将了解优化价格的方法。
但在此之前,我们必须向商店经理提出一些问题。
问:在优化价格时,是否应考虑最低和最高价格变动的限制?
根据与商店经理的讨论,我们确定价格下降不得超过 20%,价格上涨也应限制在 20%以内。
目前 Yochips 的价格是$3.23,现在我们知道优化价格必须在$2.58 — $3.876 之间。但我们如何得出优化价格呢?
但是我们如何得出一个最大化收益的优化价格呢?让我们做些数学运算:-
优化收益 = 总售出单位 * (优化价格)
我们需要优化价格以最大化收益。但总售出单位也会随着价格变化而变化。让我们重新写一下上述方程,并将总售出单位称为优化单位:-
优化收益 = 优化单位* (优化价格)……………(eq1)
我们已经知道 —
弹性 = 售出单位的百分比变化/价格的百分比变化
因此:-
优化单位 = 基准单位 + 优化价格下的单位变化
这里,基准单位指的是当前价格为$2.58 时的总销售单位
优化单位 = (基准单位 + (基准单位 * 价格弹性 * (优化价格与常规价格的百分比变化) ………. (eq2)
让我们将 eq2 代入 eq1 中
优化收益 = (基准单位 + (基准单位 * 价格弹性 * (优化价格与常规价格的百分比变化) * (优化价格) ……… (eq3)
优化收益 = (基准单位 + (基准单位 * 价格弹性 * [(优化价格 — 当前价格)/ 当前价格]* (优化价格)……………eq(4)
以下是优化方程(eq4)中的关键参数:
基准单位 = 当前价格下的平均销售单位。
价格弹性 = 计算得出的物品价格弹性值
当前价格 = 最新销售价格
很好!在我们的方程式中,除了优化价格外,我们还有所有其他变量的数据。那么我们可以使用哪种算法来计算一个最大化收益的优化价格?我们可以简单地使用优化算法。
优化所需的关键组件是什么:-
-
需要最小化/最大化的目标函数:- 我们已经定义了目标函数,即最大化优化收益,如方程(eq4)所定义。
-
边界:根据店铺经理的定义,我们需要优化价格变化不超过 20%。因此,下限 = 当前价格(1–0.2),上限 = 当前价格(1+0.2)
-
优化算法:我们将使用 Python 中的 Scipy.optimize 库来实现优化。
让我们看看代码:-
# Taking latest 6 weeks average of the base sales
#--------------------------------------------------
# Ranking the date colume
df_item_store_optimization["rank"] = df_item_store_optimization["ds"].rank(ascending=False)
# Subset latest 6 weeks of data
base_sales_df = df_item_store_optimization.loc[df_item_store_optimization["rank"] <= 6].groupby("id")["base_sales"].mean().reset_index()
df_item_store_optimization_input.rename(columns = {"base_sales":"base_units"}, inplace=True)
# Deriving the min and max bound for the sell_price
#--------------------------------------------------
# Creating UB and LB as with the range of 20%
df_item_store_optimization_input["LB_price"] = df_item_store_optimization_input["sell_price"] - (0.2*df_item_store_optimization_input["sell_price"])
df_item_store_optimization_input["UB_price"] = df_item_store_optimization_input["sell_price"] + (0.2*df_item_store_optimization_input["sell_price"])
上述代码帮助我们准备数据以进行优化。首先,我们将计算基准单位,即最近 6 周base_sales(分解系列的趋势成分)的平均值。我们已经在 t节中讨论了计算base_sales的方法。
接下来,我们通过分别从当前销售价格减少和增加 20%来定义LB_price和UB_price。
让我们定义执行优化的代码。
from scipy.optimize import minimize
# Define the objective function to be minimized
def objective(opti_price):
df_item_store_optimization_input["opti_price"] = opti_price
df_item_store_optimization_input["optimized_units"] = df_item_store_optimization_input["base_units"] + (df_item_store_optimization_input["base_units"]*\
((df_item_store_optimization_input["opti_price"]/df_item_store_optimization_input["sell_price"]) - 1)*\
(df_item_store_optimization_input["price_elasticity"]))
df_item_store_optimization_input["optimized_revenue"] = df_item_store_optimization_input["optimized_units"]*df_item_store_optimization_input["opti_price"]
return -sum(df_item_store_optimization_input["optimized_revenue"])
# Define the initial guess
opti_price = df_item_store_optimization_input["sell_price"][0]
# Define the bounds for the variables
bounds = ((df_item_store_optimization_input["LB_price"][0], df_item_store_optimization_input["UB_price"][0]),)
# # Use the optimization algorithm to minimize the objective function
result = minimize(objective, opti_price, bounds=bounds)
# Print the optimization result
print(result)
上述代码将为我们提供优化价格。你能猜到在目标函数中为什么定义了负优化收益吗?-(-1)是什么?是 1。我们在最小化目标函数,使用负号表示优化收益将导致最大化优化收益。
此外,我们可以用任何随机值初始化opti_price变量,仅仅为了促进快速收敛,我们将其初始化为当前的sell_price。在边界中,我们定义了在上述代码中创建的LB和UB。
万岁!我们已经找到了 Yochips 的优化价格,并准备向加州的店铺经理提议。
图片由作者提供
我们的建议是将 Yochips 的价格降低 10.2%至$2.9。这将带来最大的收益。
这是价格优化方法的最后一步,这种整体方法非常强大,可以帮助我们为每个店铺的每个商品返回优化价格。
上述方法的一个局限性是对于我们没有足够价格变动历史的商品。在这种情况下,我们使用其他技术,但如果这种商品的比例较少,则可以使用类别级别的平均价格弹性。
希望你喜欢这篇文章!现在你知道如何进行价格优化了。
图片由作者提供(字体由 onlinewebfonts 制作)
掌握回归分析的艺术:每个数据科学家应该了解的 5 个关键指标
关于回归分析中使用的度量的所有知识的权威指南
·发布在 Towards Data Science ·阅读时间 14 分钟·2023 年 2 月 20 日
–
由作者在 Dall-E 上根据提示“一个未来派机器人在黑板上教数学”创建的图像。
在监督学习的情况下,我们可以将机器学习问题细分为两大类:回归问题和分类问题。
在本文中,我们将讨论在回归分析中使用的五个度量,以了解模型是否适合解决特定的机器学习问题。
但首先,让我们回顾一下回归分析是什么。
回归分析是一种数学技术,用于寻找因变量与一个或多个自变量之间的函数关系。
在机器学习中,我们将“特征”定义为自变量,将“标签”(或“目标”)定义为因变量,因此回归分析的目的是找到特征与标签之间的估计(一个好的估计!)。
**Table Of Contents**
The residuals
1\. The mean squared error (MSE)
2\. The root mean square error (RMSE)
3\. The mean absolute error (MAE)
4\. The Coefficient of Determination (R²)
5\. The adjusted R²
Calculating all the Metrics in Python
残差
在讨论度量之前,我们需要讨论残差。
为了简化起见,我们考虑线性回归模型(但结果可以推广到任何其他机器学习模型)。
比如,假设我们有一个数据集,其中数据以某种线性方式分布。我们通常会遇到如下情况:
回归线。作者提供的图像。
红线称为回归线,它是我们将用来进行预测的线。如我们所见,数据点并不完全位于回归线上,因此我们可以将残差定义为回归线(预测值)与实际数据点之间的误差,方向为垂直方向。
因此,对于上述图像,我们从数学上定义残差为:
残差的定义。图像由作者提供。
我们希望得到 e_i=0
,因为这意味着所有数据点都恰好位于回归线上的,但不幸的是,这不可能实现,因此我们在回归问题中使用以下度量来验证我们的 ML 模型。
我们定义 “帽子” y 为 拟合值 或 预测值(由模型拟合/预测:在这种情况下是线性回归模型),而 y 是 真实值。因此,预测值可以计算为:
如何计算预测值。图像由作者提供。
在上述公式中,系数 w(称为 权重)和 b(称为 偏置 或 常数)是估计值,这意味着它们在学习过程中由 ML 模型学习得到。
这些知识很重要,因为现在我们可以将 残差平方和(RSS) 定义为:
残差平方和的公式。图像由作者提供。
现在,如果我们将之前看到的预测值公式代入括号中,我们得到:
残差平方和的扩展公式。图像由作者提供。
估计的系数 w 和 b 是那些最小化 RSS 的系数。
实际上,我们必须记住,学习过程要求所选的 度量(也称为 成本函数 或 损失函数)必须被最小化。
在数学中,最小化一个函数意味着计算它的导数并将其等于 0。因此,我们应该执行类似的操作:
RSS 函数对 w 的导数。图像由作者提供。
和
RSS 函数对 b 的导数。图像由作者提供。
我们不会在这里进行计算;请考虑这些计算的结果是:
最小化 RSS 函数的值。图像由作者提供。
在上述公式中,带“上划线”的 x 和 y 是均值。因此它们可以计算为:
x 的均值(也适用于 y)。图像由作者提供。
现在,考虑到这些,我们将定义和计算 5 个成本函数。
我们将使用表格中的 5 个数字来展示各种度量之间的差异。该表格包含以下内容:
-
真实值。
-
预测值(由线性回归模型预测的值)。
我们将用于后续计算的表格。图像由作者提供。
**NOTE**: consider these data as calculated on the train set. In the following
calculations we'll give for granted that we refer just to the train set
and we won't discuss the test set.
1. 均方误差(MSE)
我们定义 均方误差 (MSE) 如下:
MSE 的定义。图片来源:作者。
其中 n 是观察值的数量。换句话说,它表示我们总共有多少个值。在我们的例子中,由于我们有一个包含 5 个数字的表格,因此 n=5。
MSE 衡量预测值与实际值之间的平均平方差。换句话说,它告诉我们我们的预测值与实际值的平均偏差。
让我们根据表格中的值进行计算:
使用给定数字计算 MSE。图片来源:作者。
我们得到:MSE = 51.2
2. 均方根误差 (RMSE)
均方根误差 (RMSE) 只是 MSE 的平方根;所以它的公式是:
RMSE 的定义。图片来源:作者。
现在,让我们考虑上述表格中的值,并计算 RMSE:
使用给定数字计算 RMSE。图片来源:作者。
MSE 和 RMSE 之间没有很大的差别。它们指的是相同的量,唯一的数学区别在于 RMSE 有一个平方根。然而,RMSE 更容易解释,因为它与输入值(预测值和真实值)具有相同的单位,因此更直接地与它们进行比较。
让我们举个例子来理解这个问题。
想象一下,我们已经训练了一个线性回归模型,以预测房屋的价格,基于其面积和卧室数量。我们计算 MSE 和 RMSE 的值并进行比较。
假设模型预测一座 1000 平方英尺、2 间卧室的房子的价格为 200,000 美元。然而,房子的实际价格为 250,000 美元。我们将得到:
房子的价格的 MSE(在这种情况下,n=1,因为我们只计算了一个值)。图片来源:作者。
和
房子的价格的 RMSE(在这种情况下,n=1,因为我们只计算了一个值)。图片来源:作者。
所以,这里要点是:RMSE 很容易与输入数据进行比较,因为在这种情况下,我们如何解释 USD² 作为度量单位?这是无法解释的,但它是正确的!
所以,这就是这两个指标之间的区别。
3. 均值绝对误差 (MAE)
均值绝对误差 (MAE) 是另一种计算实际数据点和预测数据点之间距离的方法。其公式是:
MAE 的定义。图片来源:作者。
这里,实际值和估计值之间的距离是通过范数(有时称为“曼哈顿距离”)计算的。
从公式中我们可以看到,尽管 MAE 也与输入值具有相同的单位,因此也容易解释。
现在,让我们考虑表格中的值,并计算 MAE:
使用给定数字计算 MAE。作者提供的图像。
我们得到:MAE = 5.6。
现在,在解释其他两个指标之前,我们需要说一下上面提到的三个指标。
我们已经看到 MAE 和 RMSE 比 MSE 更容易解释,因为我们得到的结果与输入数据具有相同的单位,但这并不是唯一需要说明的事。
另一个需要说明的是,任何这些指标接近 0 的值都表示模型的预测更接近实际值;换句话说,模型对数据的预测效果很好。
相反,远离 0 的值表明模型的预测值远离实际值;换句话说,模型对数据的预测效果很差。
另一个我们可以说的事是,MSE 和 RMSE 对异常值非常敏感,因为它们基于预测值和真实值之间的平方差。在实际值与预测值之间有少量大误差的情况下,平方误差将非常大,这会显著影响 MSE 和 RMSE。在这些情况下,使用对异常值不那么敏感的 MAE 可能更为合适。
如果我们分析上面的表格,可以看到第五个数据点的预测值非常偏离(真实值为 50,而预测值为 64),这对 MSE 产生了显著影响,但对 MAE 的影响较小,如我们从获得的结果中所见。
所以,我们首先要做的事情之一是正确处理异常值(这里有一篇文章解释了如何做到这一点)。
另一点需要考虑的是,我们不会仅仅使用一个模型来解决我们的机器学习问题:通常,我们从 4 到 5 个模型开始,调整它们的超参数,最后选择最佳模型。
但作为起点,如我们所知,我们无法为 4 到 5 个模型计算 MAE、MSE 和 RMSE,因为这会耗费大量时间。
所以,让我们看看我们通常面临的情况:我们决定使用 5 个机器学习模型的池,并且例如,我们计算了 MAE 并得到了以下结果:
-
ML_1 的 MAE:115
-
ML_2 的 MAE:351
-
ML_3 的 MAE:78
-
ML_4 的 MAE:1103
-
ML_5 的 MAE:3427
我们知道 MAE 的值(但这同样适用于 MSE 和 RMSE)必须尽可能接近 0;因此,我们可以立即理解 ML_1 和 ML_3 在我们选择的 5 个模型中表现最好,但问题是:它们有多好?
这些指标中的每一个都可以达到任何值,甚至 100 万或更多。我们只知道我们必须尽可能接近 0 才能说我们的模型在解决这个机器学习问题上表现良好;但是结果必须离 0 有多近?MAE 为 78 足以说明 ML_3 在解决这个机器学习问题上非常好吗?
因为这些指标都可以达到任意值,统计学家定义了另外两个值在 0 到 1 之间的指标。这对于一些数据科学家在比较不同模型的结果时可能更有帮助。
4. 决定系数(R²)
我们定义 决定系数(或 R²)如下:
决定系数的定义。图片由作者提供。
在这里,我们之前定义了 RSS 为残差平方和。然后我们得到了总平方和,其定义为:
总平方和的定义。图片由作者提供。
TSS 简单来说就是预测变量 y 的方差;实际上,我们可以将分子和分母都乘以并除以 1/n:
决定系数的修改定义。图片由作者提供。
现在,分子正好是 MSE,分母是 y 的方差;所以我们可以写:
决定系数的另一种定义方式。图片由作者提供。
如果 R²=1,这意味着 MSE=0,因此模型完全符合数据。相反,R²=0 表示我们的模型完全不符合数据。
R² 的范围在 0 和 1 之间,正如我们希望的,但仅适用于训练集。这意味着 TSS>RSS 或 var(y)> MSE。相反,在测试集上,R² 可能变成负值,这意味着我们的模型对测试集的拟合很差(但我们在这里不会进一步讨论)。
现在,让我们回顾一下之前的工作。使用上面提供的表格,我们得到了:
-
MSE = 51.2
-
RMSE = 7.15
-
MAE = 5.6
因此,从 RMSE 和 MAE 来判断,我们用于这些计算的(唯一)模型似乎很好,因为我们接近 0。
但如果你熟悉数学分析,你可能会同意 5.6 可以认为离 0 很远。这是因为我们没有参考标准来进行判断。
现在,让我们看看计算 R² 会发生什么。
让我们计算 y 的均值:
表中提供的 y 的均值。图片由作者提供。
现在我们可以计算方差:
表中提供的 y 的方差。图片由作者提供。
我们之前计算了 MSE(MSE = 51.2),所以,最后我们有:
使用提供的值计算决定系数。图片由作者提供。
记住,在训练集上,R² 的范围在 0 和 1 之间,且离 1 越近模型越好,一般认为 R² 为 0.7 或更高是一个良好的拟合。
因此,我们可以立即且毫无疑问地说,我们的模型对数据的拟合相当好,因为我们知道我们可以获得的最佳值是 1,由于我们得到了 0.739,我们可以说,就比较而言,这个结果相当不错。
R²的问题在于,当我们向模型中添加额外的解释变量时,它往往会增加。这是因为额外的变量可能会改善模型的拟合度。因此,当我们向模型中添加更多的解释变量时,它对预测变量的信息更多,这可能使预测更准确。然后,这可能导致预测变量的方差减少,从而使 R²增加。
要确定一个变量是否对我们的模型具有解释性,我们必须考虑它是否可能对因变量产生影响。例如,如果我们研究收入与幸福之间的关系,假期花费的钱可能被认为是一个解释变量,因为它可能对幸福产生影响。另一方面,受访者的汽车颜色在这种情况下可能不会被认为是解释变量,因为它不太可能对幸福产生影响。
为了应对 R²这种行为,统计学家定义了调整后的 R²。
5. 调整后的 R²
调整后的 R²是 R²的一种特殊形式,用于修正由于模型中新增解释变量而可能导致的 R²过高的情况。我们可以定义为:
调整后的 R²的定义。图像由作者提供。
其中:
-
n是我们数据中的样本数量。
-
p是特征的数量(在回归问题中有时称为预测变量:这就是我们使用字母p的原因)。
假设我们有一个包含 2 个自变量和 10 个样本的模型,该模型的 R²为 0.8。我们有:
调整后的 R²的计算。图像由作者提供。
通常,当模型中有大量自变量时,建议使用调整后的 R²,因为它比“标准”R²提供了更准确的度量。
在 Python 中计算所有指标
幸运的是,在 Python 中我们无需计算这些指标:sklearn
库会为我们计算,除了调整后的 R²:在这种情况下,我们必须通过编码来计算公式的参数。
让我们看一个例子。我们生成一些随机数据,用线性回归模型拟合训练集,并打印所有指标的结果。
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
# generate random data
np.random.seed(42)
X = np.random.rand(100, 5)
y = 2*X[:,0] + 3*X[:,1] + 5*X[:,2] + np.random.rand(100)
# split data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=42)
# fit linear regression model to the train set
reg = LinearRegression()
reg.fit(X_train, y_train)
# make predictions on the train set
y_pred_train = reg.predict(X_train)
# calculate metrics on the train set with 2 decimals
mae_train = round(mean_absolute_error(y_train, y_pred_train), 2)
mse_train = round(mean_squared_error(y_train, y_pred_train), 2)
rmse_train = round(np.sqrt(mse_train), 2)
r2_train = round(r2_score(y_train, y_pred_train), 2)
# calculate adjusted r-squared on the train set with 2 decimals
n = X_train.shape[0] #number of features
p = X_train.shape[1] #number of predictors
adj_r2_train = round(1 - (1 - r2_train) * (n - 1) / (n - p - 1), 2)
# print the results
print("Train set - MAE:", mae_train)
print("Train set - MSE:", mse_train)
print("Train set - RMSE:", rmse_train)
print("Train set - r-squared:", r2_train)
print("Train set - adjusted r-squared:", adj_r2_train)
>>>
Train set - MAE: 0.23
Train set - MSE: 0.07
Train set - RMSE: 0.26
Train set - r-squared: 0.98
Train set - adjusted r-squared: 0.98
现在,在这种情况下,R²和调整后的 R²之间没有差异,因为数据是故意创建的,而且我们只有 5 个特征。
这段代码只是为了展示我们如何在 Python 中将本文所学的知识应用于实际案例。
此外,我们可以清楚地看到 MAE、MSE 和 RMSE 接近 0 的含义。由于 R² 是 0.98,实际上这些指标是“0.xx”,这比我们在表格示例中找到的 5.6 更接近 0。
结论
到目前为止,我们已经全面了解了与机器学习回归分析相关的所有指标。
即使我们写了很长的文章,我们希望这能帮助读者更好地理解这些指标的底层内容,更好地理解如何使用它们,以及它们之间的区别。
你可能也喜欢:
成为掌握机器学习分类指标的唯一指南
towardsdatascience.com
免费 Python 电子书:
开始学习 Python 数据科学但感到困惑? 订阅我的新闻通讯,获取我的免费电子书:这将为你提供正确的学习路径,以通过实践经验学习 Python 数据科学。
享受这个故事了吗?成为 Medium 会员每月 5$ 通过我的推荐链接:我会获得小额佣金,对你没有额外费用:
[## 通过我的推荐链接加入 Medium — Federico Trotta
阅读 Federico Trotta 的每一个故事(以及 Medium 上成千上万其他作家的故事)。您的会员费用直接支持…
medium.com](https://medium.com/@federicotrotta/membership?source=post_page-----1e2a8a2936f5--------------------------------)
Python:
-
Python 中的循环和语句:深入理解(附示例)
-
Python 循环:如何在 Python 中迭代的完整指南
-
学习的 5 个 Python 库来开始你的数据科学职业生涯
-
如何学习 Python 数据科学
数据科学:
-
如何处理数据科学中的缺失值
-
如何在数据科学项目中进行特征选择
-
如何在数据科学项目中检测异常值
-
条形图和直方图有什么区别?
-
相关性与回归的区别
-
理解 l1 和 l2 正则化
掌握数据科学工作流程
图片由 Aron Visuals 提供,来源于 Unsplash
通过这六个简单阶段自信地导航你的数据科学项目!
·
关注 发表在 Towards Data Science · 5 分钟阅读 · 2023 年 9 月 16 日
–
介绍
在今天的数据驱动世界中,我们必须在信息的广阔海洋中航行,以提取有价值的见解。为了安全地引导我们穿越这些具有挑战性的水域,我们需要一个可靠的指南针:数据科学工作流程。
数据科学工作流程是什么?
数据科学工作流程是一个结构化的阶段框架,指导数据科学家有效地应对数据科学项目的复杂性。
阶段
**1) 定义
-
收集
-
准备
-
探索
-
分析
-
沟通**
重要性
数据科学工作流程使数据科学家在从数据中提取价值时能够高效而有效地合作。
挑战
数据科学工作流程本质上是迭代的,因此当新的见解出现时,认识到需要重新审视早期阶段至关重要。
替代框架
数据科学工作流程没有一刀切的方法,因此本文提供了一种个性化的看法,借鉴了广泛认可的框架,如 CRISP-DM 和 OSEMN。
图片由 Brett Jordan 提供,来源于 Unsplash
1) 定义
定义阶段涉及明确地概述项目,以确保努力、期望和资源与共同的目标和方向一致。
技巧
背景 收集与项目相关的背景信息(例如原因、目标、问题、期望、影响)
目标 定义期望的结果、可测量的目标和关键问题,然后将任务拆分为不同的、可管理的组件
约束 通过考虑重要因素来确定项目的限制(例如资源可用性、时间限制、数据访问性、伦理考量)
图片由 Fer Troulik 提供,来源于 Unsplash
2) 收集
收集阶段涉及获取必要的数据,以便基于准确的信息进行有意义的分析。
技巧
数据需求 定义完成项目所需的数据(例如格式、变量、时间范围、粒度)
数据来源 寻找可靠且相关的数据来源(例如数据库、API、文件、传感器读数)
认证 确保获得访问数据所需的权限(例如电子邮件/密码、OAuth、API 密钥、robots.txt)
收集 使用适当的方法获取数据(例如 SQL 查询、API 调用、网页抓取、手动数据输入)
数据管理 按照最佳实践处理数据(例如数据质量、数据治理、数据安全)
图片由 Darren Ahmed Arceo 提供,来源于 Unsplash
3) 准备
准备阶段涉及处理原始数据,以实现一致且结构化的格式,适合进行可靠的分析。
技巧
数据清洗 识别和处理数据中的错误和不一致(例如缺失值、重复条目、异常值、数据格式)
数据集成 从多个来源合并数据,同时确保一致性(例如变量、命名约定、索引)
特征工程 从原始数据中提取有意义的特征(例如特征选择、特征创建、数据转换)
4) 探索
探索阶段涉及理解数据的主要特征,以便提出有效的假设、识别问题并完善项目定义。
技术
分布分析 检查每个变量的分布(例如均值、中位数、标准差、偏度、异常值)
依赖分析 调查和量化变量关系,以了解它们如何相互影响(例如相关性、交互作用、协方差、时间序列分析)
数据分割 使用各种分段和子集探索数据,以了解模式在不同组之间的变化
假设生成 生成初步见解以发展关于关系和模式的假设
照片由 Julia Koblitz 提供,来自 Unsplash
5) 分析
分析阶段涉及对数据进行深入检查,以开发一个能够产生有价值见解的稳健解决方案。
技术
假设检验 应用显著性测试以评估观察到的模式和关系的统计重要性(例如 t 检验、方差分析、卡方检验)
高级技术 利用与特定假设相关的高级算法(例如时间序列分析、回归分析、异常检测)
建模 选择、构建和评估适当的模型,并使用相关指标识别最佳配置,同时考虑复杂性、可解释性和性能等权衡因素
照片由 Patrick Fore 提供,来自 Unsplash
6) 交流
交流阶段涉及向利益相关者展示项目及其发现,以创建清晰性和意识。
技术
模型部署 部署模型以供实际使用(例如创建 API、构建 Web 应用程序、集成到现有系统中)
监控与记录 实施模型使用过程中的性能跟踪和问题记录
文档 创建全面的项目文档,涵盖技术细节(例如:模型架构、数据来源、假设、局限性)
报告和展示 生成并提供简洁、信息丰富且引人入胜的项目总结(例如:目标、方法、结果、见解、关键发现)
图片由 Jordan Madrid 提供,来源于 Unsplash
结论
数据科学工作流程是一个重要的工具,因为它为复杂的项目提供了结构和组织,从而改善决策制定、增强协作和提高准确性。
数据科学是一个动态领域,虽然工作流程提供了坚实的基础,但应根据具体项目的需求和目标进行调整。
采用并应用数据科学工作流程将使数据科学家能够优化他们的过程,并在不断变化和不断增长的数据海洋中蓬勃发展。
参考文献
[1] J. Saltz, 什么是数据科学工作流程? (2022), 数据科学过程联盟
[2] P. Guo, 数据科学工作流程:概述与挑战 (2013), ACM 通讯
[3] Springboard, 数据科学过程 (2016), KDNuggets
[4] S. Gupta, 数据科学过程:初学者的简明指南 (2022), Springboard
[5] M. Tabladillo, 团队数据科学过程生命周期 (2022), 微软
[6] D. Cielen, A. Meysman, M. Ali, 介绍数据科学 — 第二章:数据科学过程 (2016), 曼宁出版
[7] Z. Awofeso, 初学者的数据科学项目工作流程结构指南 (2023), Analytics Vidhya
[8] N. Hotz, 什么是 CRISP-DM? (2023), 数据科学过程联盟
[9] J. Brownlee, 如何像数据科学家一样解决问题 (2014), 机器学习精通
掌握未来:评估利用 IaC 技术生成 LLM 数据架构
评估 LLM 在生成基础设施代码以配置、配置和部署现代应用程序方面的适用性
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 10 月 16 日
–
照片由 ZHENYU LUO 提供,出处为 Unsplash
介绍
在这篇文章中,我们探讨了大型语言模型(LLMs)在利用真实应用程序生命周期方面的适用性,包括从基础设施提供到配置管理和部署。由此产生的源代码在 GitHub¹¹ 上公开可用。基础设施即代码(IaC)解决方案通过代码而非手动过程来促进基础设施的管理和提供¹。它们正变得越来越普遍,主要的云服务提供商已实现了自己的 IaC 解决方案,以与其服务互动。在这方面,AWS CloudFormation、Google Cloud Deployment Manager 和 Azure Resource Manager Templates 简化了云服务的提供,消除了 IT 运营手动建立服务器、数据库和网络的需求。然而,这些多种可能性引入了供应商锁定的风险,因为为特定云提供商定义的 IaC 不能移植,如果需要不同的云提供商,则需要进行翻译。在这方面,像 Terraform² 或 Pulumi³ 这样的工具提供了对不同云提供商各种实现的抽象,并促进了可移植部署的开发。这样,供应商锁定的风险显著降低,组织可以动态响应其需求,而无需承担巨大的实施成本。此外,IaC 技术还带来了许多好处⁴:
-
一致性:它允许通过实现可重复的部署来自动化基础设施提供。
-
降低风险:它通过最小化人工干预,促成了一种更少出错的基础设施管理方法。
-
成本优化:它使得识别不必要资源更容易,并能够更快地在云提供商之间迁移,以应对计费变化。
-
改进的协作:脚本可以集成到版本控制工具中,这促进了个人和团队之间的协作。
然而,应用生命周期超出了基础设施提供的范围。下图显示了不同 IaC 技术支持的应用生命周期⁵。
基础设施即代码技术支持的应用生命周期。| 来源:Josu Diaz-de-Arcaya 等⁵
在这种情况下,IaC 技术的目标超出了简单的基础设施资源提供。在建立了必要的基础设施后,配置管理阶段确保所有要求都得到适当安装。这个阶段通常使用工具如 ansible⁶、chef⁷ 或 puppet⁸ 来完成。最后,应用部署阶段负责在各种基础设施设备上进行应用的协调。
了解 LLMs
大型语言模型(LLMs)是指一类人工智能模型,它们旨在根据提供给它们的输入理解和生成类似人类的文本。这些模型以其大量的参数而闻名,这使它们能够捕捉语言中复杂的模式和细微的差异⁹。
-
文本生成:LLMs 生成的文本可以与其周围的内容连贯且相关。它们被用于完成文本、生成材料,甚至进行创意写作活动。
-
语言理解:LLMs 能够理解并从文本中提取信息。它们能够进行态度分析、文本分类和信息检索。
-
翻译:LLMs 可以将文本从一种语言翻译成另一种语言。这对机器翻译应用非常有益。
-
回答问题:LLMs 可以根据给定的上下文回答问题。它们用于聊天机器人和虚拟助手以回答用户的查询。
-
文本摘要:LLMs 可以将长篇文章总结为更短、更连贯的摘要。这对于快速消化信息非常有用。
在上述能力中,我们将专注于文本生成。特别是在根据输入提示生成正确的 IaC 代码方面,大型语言模型(LLMs)在自然语言处理领域取得了显著进展,但也提出了几个挑战和关注点。当时与 LLMs 相关的一些关键问题和关注点包括:
-
偏见和公平性:LLMs 可以学习数据中存在的偏见,这可能导致有偏见或不公平的结果。
-
错误信息和虚假信息:LLMs 可能生成虚假或误导性信息,这可能促成在线错误信息和虚假信息的传播。这些模型有潜力创建看似可信但事实上不正确的内容。
-
安全和隐私:LLMs 可能被误用来生成恶意内容,例如深度伪造文本、假新闻或钓鱼邮件。
下表显示了各种 LLMs 的比较¹⁰。
使用 LLMs 生成 IaC
为了测试当前 LLM 工具在 IaC 领域的表现,设计了一个用例。最终目标是使用 FastAPI 框架在虚拟机中构建一个 API,允许客户端使用 HTTP 方法在 Elasticsearch 集群中执行搜索和删除任务。该集群将由三个节点组成,每个节点都在自己的虚拟机中,并且在另一台机器上将是 Kibana,这是支持可视化的集群管理工具。所有内容都必须在 AWS 云中完成。下图显示了这个架构:
设计用例,旨在测试 LLMs 生成 IaC 的可行性。
挑战是使用 LLM 工具成功完成以下三个任务。在这篇文章中,我们使用了 OpenAI 的 ChatGPT。
-
用于在 AWS 上构建五个虚拟机器的 Terraform 代码。
-
用于通过 API 的 HTTP 方法执行文档搜索和删除操作的 FastAPI 应用的源代码。
-
用于在三个节点上部署和安装 Elasticsearch 集群、在另一个节点上部署 Kibana、在剩余节点上部署 FastAPI 应用的 Ansible 代码。
任务 #1
对于这个挑战,我们使用了以下提示:
我需要通过 Terraform 创建五个虚拟机器,选择你想要的公共云提供商。这些虚拟机器的目的如下:其中三个用于部署 Elasticsearch 集群,每天处理 2G 的数据;另一个用于 Kibana;最后一个用于部署 FastAPI 应用。你应该为每个虚拟机器选择硬件和云提供商。对于你不知道的变量,使用占位符变量。选择便宜的实例。
初始回应是一个不错的尝试,但我们需要不断迭代。例如,我们希望所有的变量都定义在一个单独的文件中,这导致了以下图像。
variables.tf 的代码片段,包含了需要配置的变量。
同样,我们希望知道部署的 IP 地址,并且我们希望这个配置在一个单独的文件中。
output.tf 的代码片段,包含了刚刚配置好的虚拟机器的 IP 地址。
AI 在描述我们想要的实例以及为它们配置所需的安全组方面做得非常出色。
main.tf 的代码片段,包含了需要配置的虚拟机器
它还创建了我们需要的安全组所需的资源,并在定义各种端口时使用了占位符。
main.tf 的代码片段,包含了要使用的安全组。
总的来说,ChatGPT 在完成这个任务时表现得很好。然而,我们花了一段时间来获得一个可行的配置,确保网络配置正确。例如,我们希望连接到每个配置好的虚拟机器,这一点我们以如下方式进行说明。
我希望从我的笔记本电脑上对它们进行 ssh 访问,并且 Kibana 实例需要从我的笔记本电脑上进行 http 和 https 访问。
上述提示生成的代码几乎正确,因为 AI 对 ingress 和 egress 策略感到困惑。然而,这一点很容易发现并修正。
在能够访问虚拟机器后,我们遇到了由于权限不足而无法连接的问题。这导致了更长时间的对话,最终我们发现自己添加这些行更容易。
任务 #2
对于这个挑战,我们使用了以下提示:
我需要创建一个 FastAPI 应用程序。这些 API 的目的是提供存储单个 JSON 文档到 Elasticsearch 集群、存储多个文档以及删除文档的方法。Elasticsearch 集群部署在 3 个节点上,并且具有基本的身份验证,用户名为“tecnalia”,密码为“iac-llm”。
这个提示的结果非常成功。该应用程序使用 Elasticsearch python 包¹²与 Elasticsearch 集群进行交互,完全有效。我们只需要记住要更改部署集群的节点的 IP 地址。在下图中,第一个方法是用于在 Elasticsearch 中插入单个文档。
存储单个文档方法的代码摘录。
然后,第二个方法用于在一次调用中批量插入多个文档。
存储多个文档方法的代码摘录。
最后,最后一个方法可以用于从 Elasticsearch 集群中删除单个文档。
删除文档方法的代码摘录。
我们认为这个实验非常成功,因为它正确地选择了适合执行任务的库。然而,仍需进一步手动调整以将这段代码变成生产就绪的软件。
任务 #3
对于这个挑战,我们使用了以下提示:
生成 Ansible 代码以在三个节点上安装 Elasticsearch 集群。请同时添加一个连接到集群的 Kibana 节点。
这个提示在生成所需的 Ansible 脚本方面表现尚可。它在将源代码组织到各种文件中方面做得非常出色。首先是包含所有节点详细信息的清单。请记住,此文件需要根据任务 #1 生成的正确 IP 地址进行调整。
inventory.ini 的代码摘录
然后,安装 Elasticsearch 的主要 Ansible 脚本在以下图中显示。这是它的一个摘录,完整示例可以在仓库¹¹中找到。
elasticsearch_playbook.yml 的代码摘录
另一方面,每个 Elasticsearch 节点所需的配置已经方便地生成了 Jinja 文件。在这种情况下,我们不得不手动添加 path.logs 和 path.data 配置,因为 Elasticsearch 无法启动,原因是权限问题。
elasticsearch.yml.j2 的代码摘录。
类似地,ChatGPT 能够为 Kibana 实例生成类似的配置。然而,在这种情况下,我们手动将配置分离到一个单独的文件中以方便管理。以下图片展示了该文件的一个摘录。
kibana_playbook.yml 的代码摘录。
类似地,以下 jinja 文件涉及 Kibana 实例,虽然 IP 地址最好进行参数化,但似乎也不错。
kibana.yml.j2 的代码摘录
总体来说,我们发现 ChatGPT 在生成项目框架方面非常出色。然而,将框架转化为生产级应用程序仍需进行大量操作。在这方面,需要对所使用的技术具有深入的专业知识来调整项目。
结论
本文讨论了使用大语言模型(LLMs)来监督应用程序生命周期的情况。以下将讨论这一工作的优缺点。
优点
-
使用大语言模型(LLMs)支持应用生命周期的各个阶段在启动项目时特别有利,尤其是在知名技术方面。
-
初始框架结构良好,提供了否则不会使用的结构和方法。
缺点
-
大语言模型(LLMs)面临使用 AI 解决方案时的偏见风险;在这种情况下,ChatGPT 选择了 AWS 而非类似选项。
-
将项目打磨至生产就绪可能会很麻烦,有时手动调整代码更为方便,这需要对所使用的技术有深入了解。
致谢
本工作由巴斯克政府资助的 SIIRSE Elkartek 项目(面向工业 5.0 的鲁棒、安全和伦理的智能工业系统:规范、设计、评估和监控的先进范式)(ELKARTEK 2022 KK-2022/00007)提供资金支持。
作者贡献
概念化、分析、调查和写作是 Juan Lopez de Armentia、Ana Torre 和 Gorka Zárate 的共同努力。
参考文献
-
什么是基础设施即代码(IaC)?(2022)。
www.redhat.com/en/topics/automation/what-is-infrastructure-as-code-iac
-
Terraform by HashiCorp。(无日期)。检索于 2023 年 10 月 5 日,来自
www.terraform.io
-
Pulumi — 通用基础设施即代码。(无日期)。检索于 2023 年 10 月 5 日,来自
www.pulumi.com/
-
基础设施即代码的七大主要好处 — DevOps。(无日期)。检索于 2023 年 10 月 5 日,来自
duplocloud.com/blog/infrastructure-as-code-benefits/
-
Diaz-De-Arcaya, J., Lobo, J. L., Alonso, J., Almeida, A., Osaba, E., Benguria, G., Etxaniz, I., & Torre-Bastida, A. I.(2023)。IEM: 用于多语言 IaC 部署的统一生命周期编排器 ACM 引用格式。
doi.org/10.1145/3578245.3584938
-
Ansible 是简单的 IT 自动化。(无日期)。检索于 2023 年 10 月 5 日,来自
www.ansible.com/
-
Chef 软件 DevOps 自动化解决方案 | Chef。(无日期)。检索于 2023 年 10 月 5 日,来自
www.chef.io/
-
Puppet Infrastructure & IT Automation at Scale | Puppet by Perforce。(无日期)。检索于 2023 年 10 月 5 日,来自
www.puppet.com/
-
Kerner, S. M. (无日期)。什么是大语言模型? | TechTarget 定义。检索于 2023 年 10 月 5 日,来自
www.techtarget.com/whatis/definition/large-language-model-LLM
-
Sha, A. (2023)。2023 年 12 大最佳大语言模型(LLMs) | Beebom。
beebom.com/best-large-language-models-llms/
-
Diaz-de-Arcaya, J.,Lopez de Armentia, J.,& Zarate, G. (无日期)。iac-llms GitHub。检索于 2023 年 10 月 5 日,来自
github.com/josu-arcaya/iac-llms
-
Elastic Client Library 维护者。(2023)。elasticsearch · PyPI。
pypi.org/project/elasticsearch/
掌握未知领域与 GPT-4 和翻转互动模式
利用 GPT-4 生成高质量问题,重新定义问题解决和决策制定
·发表于 Towards Data Science ·19 min 阅读·2023 年 7 月 13 日
–
由 Ali Kazal 提供的照片,来源于 Unsplash
介绍
像我一样,你可能也被生成 AI 领域最近取得的进展所惊艳。这感觉就像我们生活在科幻现实中,机器理解了我们。
在这场革命的核心,OpenAI 的 GPT-4 已经成为语言模型领域的一项奇迹。作为曾有幸尝试过它的人,我可以证明它令人惊叹的能力。
在这篇文章中,我想分享一种让我非常感兴趣的技术。在文献中,这种技术被称为 翻转互动模式,我发现它是一个非常强大的框架,可以帮助解决问题。这个提示工程方法的独特之处在于其“倒置”的(即翻转的)方法:它并不直接要求 AI 提供答案或解决方案,而是主要集中在让 AI 提出能够有效引导我们找到期望解决方案的正确问题。
在本文中,我将带你了解它的工作原理,解释它的好处,并提供其有效性的真实案例。希望到最后,你能掌握一个有用的技巧——一个可以应用于各种问题的技巧,结果可能会令你惊讶。
为什么选择 翻转互动模式?
在今天的世界中,我们的知识深度常常可以比作一个池塘——长而窄。我们深入某些专业领域,成为某些特定领域的专家。这种超专业化使我们在各自领域中表现出色,但同时,也可能让我们在遇到自己不擅长的问题时感到迷茫。作为一名软件工程师,我可能对算法、数据结构和编码非常熟悉,但当我手握铁锹,被要求照料花园时,我将把任何绿地变成荒地。
这时,达宁-克鲁格效应可能会发挥作用。根据这一心理假设,存在一种认知偏差,即能力较低的人高估了自己执行任务的能力。本质上,我们不知道我们不知道什么,这可能导致过度自信和错误。
然而,问题不仅仅在于我们在一个陌生领域里是业余的。问题在于我们甚至无法提出正确的问题来引导我们找到解决方案。园艺时需要考虑哪些关键因素?我应该注意什么?我应该从哪里开始?这些是我们在跨出专业领域时可能不知道要问的问题。
这就是翻转互动模式的这个版本发挥作用的地方。这种方法承认解决方案的有效性取决于之前问题的质量。我们不会让 GPT 提供问题的解决方案,而是使用 GPT-4 帮助识别需要提出的问题以找到解决方案。
翻转互动模式的实际应用
让我们将这一技术应用到园艺上。挑战是什么?给我那片荒废的前院带来新生。
要应用这一模式,我们从精心设计的提示开始。这个提示为我们与 GPT-4 的对话奠定了基础。以下是基本模板:
Act as a **{{Relevant Professional}}**.
I want to **{{Problem to Solve}}**
You will ask me 10 questions, one at the time.
After each question you will wait for my answer, after my answer you
will ask the next question. When all the 10 questions
have been answered, you will write me the exact steps
I have to do to solve my problem
在我们具体的案例中,我们将使用以下初始提示:
Act as a **professional gardener**. I want to **renew a front garden that
is in a pretty bad shape, but I don't know where to start**.
You will ask me 10 questions, one at the time. After each question you
will wait for my answer, after my answer you will ask the next question.
When all the 10 questions have been answered, you will write me the exact
steps I have to do to solve my problem.
让我们分析不同的元素。
-
角色分配: 我们提示的第一部分设定了 GPT-4 将采用的角色来帮助我们解决问题——在这种情况下,是专业园艺师。为 GPT-4 分配角色是一种被广泛认可的策略,它在使 GPT 输出更相关和准确的回答方面证明了其有效性。
-
问题陈述: 我们提示的下一部分明确了我们的问题。理想情况下,我们希望包括尽可能多的细节;因为提供的上下文越多,模型可以更好地调整其问题。
-
顺序提问: 最后,我们的提示强调 GPT-4 的提问应遵循顺序格式。这一技术元素至关重要,因为它确保了 AI 的后续问题基于之前的回答。
让我们看看对话如何展开。
GAIrdening
你可以在这个 链接 找到完整的对话记录。为了参考,我也在下面写出了对话内容。
**Question 1**
*GPT-4*: What is the size of your garden area?
*Andrea*: It's around 15 squares meters. There is an area that has a weed lawn, and another area that has tiles. i only want to renew the weedy lawn, which is 15 squares meters. In that area I also have a cherry tree
**Question 2**
*GPT-4*: Could you describe the condition of the soil? Is it sandy, loamy, clayey? And do you know the pH level of your soil?
*Andrea*: It's loamy. I have no idea about the ph
**Question 3**
*GPT-4*: Do you have a preference for a certain type of plants or a specific look you want for your garden? For instance, do you want flowering plants, ornamental grasses, shrubs, perennials, or annuals?
*Andrea*: In the side I want to plant a periennals, the most part would be grass. I'd like to have some plants around the cherry tree, and a hedge on the front of the garden (the part that sees the street)
**Question 4**
*GPT-4*: What is the sunlight situation in your garden? Does it receive full sunlight, partial sun, or is it mostly in the shade?
*Andrea*: Full sun, besides the area under the tree that is mostly in shade
**Question 5**
*GPT-4*: For the hedge, do you have any particular preferences for height, thickness, and color (green, variegated, etc.)?
*Andrea*: Green High plants
**Question 6**
*GPT-4*: Could you tell me a bit more about your local climate? For instance, where are you located and what kind of temperatures, rainfall, and seasons do you experience?
*Andrea*: I live in Ireland, it rains quite a lot, but there is some level of sun almost every day.
**Question 7**
*GPT-4*: Do you have a preference in terms of maintenance? Would you prefer a low-maintenance garden or are you comfortable with investing a bit more time in upkeep?
*Andrea*: I'm comfortable in investing some time
**Question 8**
*GPT-4*: What is the main purpose of this garden? Is it mainly for aesthetics, to create a private space, to attract wildlife, or maybe for growing fruits and vegetables?
*Andrea*: Aesthetic, but I also want to support pollinators
**Question 9**
*GPT-4*: What's your budget for this project? This will help determine what type of plants, grass, and amendments you can afford.
*Andrea*: 500 euro
**Question 10**
*GPT-4*: Lastly, do you have any known allergies to specific plants, trees, or flowers that we should avoid including in your garden?
*Andrea*: No
很明显,ChatGPT 询问了重要的细节,例如花园的大小、土壤状况、光照情况、偏好的植物类型、当地气候、我对维护的准备情况(笑)、花园的主要用途、预算以及潜在的过敏反应。
回顾起来,在走过 AI 的问题之后,我很感激这些提问促使我考虑到花园翻新中可能被忽视的方面。
两个例子:
-
ChatGPT 关于阳光的询问:“你的花园阳光情况如何?是全天候阳光、部分阳光,还是大部分时间在阴影中?” 这个问题突显了理解花园光照条件的重要性,以便选择合适的植物。说实话,这是一个关键细节,如果我单独处理这个任务,很可能会忽视。
-
GPT-4 询问:“你是否对 任何已知的过敏源 (如特定植物、树木或花卉)有过敏反应,我们应该避免在你的花园中包括这些吗?”,提醒了我一个如果没有指导可能会轻易遗漏的方面。
总之,GPT-4 实施的逐步提问策略不仅加深了我对当前问题的理解,还使得 AI 能够生成更详细和个性化的解决方案。
这是 GPT 给我的关于花园翻新的最终建议:
Step 1: Clear the Area and Check the Soil
Start by removing the weeds and any other debris from the area.
To check the pH of your soil, you can purchase an inexpensive soil
testing kit from your local garden center or online.
Loamy soil is typically around neutral but knowing the exact pH will help you
choose the best plants.
Step 2: Improve the Soil
Based on your soil test results, you may need to add amendments to your soil
to improve its fertility. For loamy soil, adding compost or well-rotted
manure can improve its nutrient content.
Step 3: Plan Your Plantings
You mentioned you wanted a hedge, grass, perennials, and plants around
the cherry tree. Here are some suggestions:
Hedge: Yew (Taxus baccata) can make a good, tall green hedge.
It's evergreen and can handle Ireland's wet climate.
Grass: Ryegrass is durable and can handle full sun and frequent rain.
Perennials for sunny areas: Consider Lavender, Sedum, and Echinacea.
These will attract pollinators and can handle full sun.
Plants for the shaded area under the cherry tree: Ferns and hostas
are shade-loving and will add some texture and visual interest.
Step 4: Purchase and Plant
Purchase your chosen plants, keeping your budget in mind. Start planting with
the hedge, as it will be your primary structural element. After that,
plant the grass, then your perennials, and finally, the shade-loving plants
around the cherry tree.
Step 5: Water and Care
After planting, water everything well. Remember that new plants will need
more frequent watering until they establish. After they are established,
adjust watering based on the weather and the needs of each type of plant.
In general, the grass will likely need watering once a week, the hedge
every 1-2 weeks, and the perennials every 1-2 weeks, but adjust as needed.
The plants under the cherry tree will likely need less water due to the
shade and potential rain protection from the tree.
Step 6: Ongoing Maintenance
Mow the grass as needed, trim the hedge to maintain its shape and height,
and prune the perennials after flowering to encourage new growth.
Remove any fallen leaves or debris regularly to keep the garden tidy.
Remember, gardening is a process, and it's okay if everything doesn't
look perfect right away. Over time, as the plants grow and fill in,
you'll see your garden transform into a beautiful, pollinator-friendly
space. Enjoy the process and the result!
自动化反转互动模式
我已经多次测试了这种技术,所以我很有信心它在大多数情况下会有效,尤其是使用 GPT-4 作为 LLM(而不是 GPT-3)。接下来我们转向一种更具实验性的方式:是否可以自动化这种方法? 为了回答这个问题,我们需要一些编码(终于)。
我们将创建两个 AI 代理进行对话,一个提问,即 inquiry_persona
,另一个回答,即 response_persona
。我们将用一些指令来初始化我们的代理,使他们共享一个共同的目标。最后,我们将让这两个 AI 对话,直到 inquiry_persona
提供最终回应。值得注意的是,这些代理必须保持对话历史,记住他们自己的回答以及对方的回答。系统的组织结构如下图所示。
采用 Google Drawings 手工制作 ❤
实施
首先让我们看看如何初始化这两个代理:
inquiry_persona
代理的初始提示与我们之前看到的非常相似。由于我们将用于此次实验的模型是 GPT-3.5,因此有一些小差异,我不得不付出额外的努力使其正常工作。
response_persona
代理被初始化为相同的TASK
,并意识到其他代理是谁。
下一步是用我们需要的信息填充这些变量,以解决我们的难题。
考虑一个场景,其中一个软件工程师正在寻求加薪,并需要制定请求薪资增加的策略。此人可能会设置如下提示:
现在提示已经准备好,我们只需调用 API 并处理对话。以下是调用 OpenAI API 的函数;该函数只需返回回应,我们将手动处理此回应。
最后,我们可以编写处理对话的代码段。
如你所见,我们使用了两个对话。这样我们可以分别初始化两个代理(即设置不同的角色以定义“谁”是代理)。对话以我们的起始消息初始化,这些消息存储在main_conversation
和side_conversation
列表中。这些列表允许代理记住对话内容。
然后,代码使用complete()
函数生成每个 AI 角色的初步回应:该函数将获取完整的对话历史,并输出“下一个回应”。请注意,对于 Chat 模型(如 GPT-3.5),可以使用ChatML标记语言自定义消息/角色(这就是通过设置role
和content
在dicts
中发生的情况)。
循环是对话的核心。在每次迭代中,两位代理将看到迄今为止的整个对话历史。
让我们执行代码,看看对话如何展开。
**AI:** 1\. Can you provide specific examples of
how you have maintained a good performance over the past 5 years?
**Andrea AI:** Certainly! Over the past 5 years, I have consistently delivered high-quality software solutions on time and within budget. I have received positive feedback from clients and colleagues on my ability to understand and meet their requirements effectively. Additionally, I have actively participated in team collaborations, contributing valuable ideas and solutions to complex problems. My performance evaluations have consistently been positive, highlighting my strong work ethic, attention to detail, and ability to adapt to changing project requirements.
**AI:** 2\. Have you taken on any additional responsibilities
or projects beyond your initial job description during these 5 years?
**Andrea AI:** Yes, I have taken on additional responsibilities and projects beyond my initial job description during these 5 years. I have actively sought out opportunities to expand my skill set and contribute to the company's success. For example, I have volunteered to mentor junior engineers, providing guidance and support in their professional development. I have also taken the initiative to lead cross-functional teams on critical projects, demonstrating my ability to handle increased responsibilities and manage complex tasks. These additional responsibilities have allowed me to contribute to the company's growth and success while further developing my own expertise.
**AI:** 3\. How has your performance directly impacted the company's success or bottom line?
**Andrea AI:** My performance has directly impacted the company's success and bottom line in several ways. First, by consistently delivering high-quality software solutions, I have helped to enhance the company's reputation and client satisfaction. This has resulted in repeat business from existing clients and positive referrals, contributing to revenue growth. Additionally, my ability to meet project deadlines and stay within budget has helped to minimize project overruns and maximize profitability. I have also proactively identified and implemented process improvements, resulting in increased efficiency and cost savings for the company. Overall, my performance has played a significant role in driving the company's success and financial performance.
**AI:** 4\. Can you provide any evidence or metrics to support your claim of maintaining a good performance?
**Andrea AI:** Certainly! Here are some evidence and metrics to support my claim of maintaining a good performance:
1\. Performance evaluations: I have consistently received positive performance evaluations from my managers over the past 5 years, highlighting my strong work ethic, technical expertise, and ability to meet project goals.
2\. Client feedback: I have received positive feedback from clients on multiple projects, commending my professionalism, attention to detail, and ability to deliver high-quality software solutions that meet their needs.
3\. Project success rate: I have successfully delivered 95% of the projects assigned to me on time and within budget, demonstrating my ability to effectively manage and execute complex software development tasks.
4\. Team collaboration: I have actively participated in cross-functional team collaborations, contributing valuable ideas and solutions to complex problems. This has resulted in successful project outcomes and positive feedback from team members.
5\. Training and certifications: I have consistently pursued professional development opportunities, obtaining relevant certifications and attending training sessions to enhance my skills and stay updated with the latest industry trends.
These evidence and metrics highlight my consistent performance and dedication to delivering high-quality work, which has contributed to the success of both individual projects and the overall company.
**AI:** 5\. Have you researched the current market
value for your role and level of experience in your industry?
**Andrea AI:** Yes, I have researched the current market value for my role and level of experience in the industry. I have conducted thorough research and analysis to understand the prevailing salary ranges and compensation packages for software engineers with similar experience, skills, and responsibilities. I have taken into account factors such as industry standards, geographical location, and company size to ensure that my expectations are aligned with the market value. Based on my research, I believe that my skills and experience warrant a fair and competitive compensation package.
**AI:** 6\. Have you prepared a list of specific
reasons why you believe you deserve a raise based on your
performance and contributions to the company?
**Andrea AI:** Yes, I have prepared a list of specific reasons why I believe I deserve a raise based on my performance and contributions to the company. Here are some key points:
1\. Consistently high-quality work: Over the past 5 years, I have consistently delivered high-quality software solutions, meeting or exceeding client expectations. This has contributed to the company's reputation and client satisfaction.
2\. Meeting project goals: I have a track record of successfully meeting project deadlines and staying within budget. This has helped to minimize project overruns and maximize profitability for the company.
3\. Additional responsibilities: I have voluntarily taken on additional responsibilities beyond my initial job description, such as mentoring junior engineers and leading cross-functional teams. These contributions have added value to the company and its employees.
4\. Process improvements: I have proactively identified and implemented process improvements, resulting in increased efficiency and cost savings. These improvements have positively impacted the company's bottom line.
5\. Positive feedback: I have received positive feedback from clients and colleagues on my professionalism, attention to detail, and ability to deliver results. This feedback demonstrates the value I bring to the company.
6\. Professional development: I have consistently pursued professional development opportunities, obtaining relevant certifications and staying updated with industry trends. This investment in my skills and knowledge directly benefits the company.
Based on these reasons, I believe that my performance and contributions warrant a raise in recognition of my value to the company.
**AI:** 7\. Have you scheduled a meeting with your manager
to discuss your request for a raise?
**Andrea AI:** No, I have not scheduled a meeting with my manager yet to discuss my request for a raise. However, I am planning to request a meeting in the near future to have a conversation about my performance, contributions, and the possibility of a raise. I believe it is important to have a formal discussion with my manager to present my case and provide them with the opportunity to consider my request.
**AI:** 8\. Have you prepared a well-structured and persuasive
argument to present during the meeting with your manager?
**Andrea AI:** Yes, I have prepared a well-structured and persuasive argument to present during the meeting with my manager. My argument highlights my consistent performance, contributions to the company, and the market value for my role and level of experience. Here is an outline of my argument:
1\. Opening: Express gratitude for the opportunity to discuss my performance and contributions with my manager.
2\. Recap of performance: Provide a brief summary of my performance over the past 5 years, emphasizing my consistent delivery of high-quality work, meeting project goals, and positive feedback received from clients and colleagues.
3\. Additional responsibilities: Discuss the extra responsibilities I have taken on voluntarily, such as mentoring junior engineers and leading cross-functional teams, and how they have added value to the company.
4\. Process improvements: Highlight the process improvements I have identified and implemented, resulting in increased efficiency and cost savings for the company.
5\. Market value research: Share the research I have conducted on the current market value for my role and level of experience, demonstrating that my expectations are aligned with industry standards.
6\. Financial impact: Discuss how my performance and contributions have directly impacted the company's success and bottom line, providing specific examples of projects, client satisfaction, and cost savings.
7\. Professional development: Highlight my commitment to continuous learning and professional development, emphasizing how it benefits both myself and the company.
8\. Request for a raise: Clearly and confidently state my request for a raise, based on my performance, contributions, and market value.
9\. Closing: Express my commitment to the company and reiterate that my intention is not to leave, but rather to be fairly compensated for my value and contributions.
By following this well-structured argument, I believe I can effectively present my case and persuade my manager to consider my request for a raise.
**AI:** 9\. Are you prepared to negotiate and consider
alternative forms of compensation if a salary increase is not
immediately possible?
**Andrea AI:** Yes, I am prepared to negotiate and consider alternative forms of compensation if a salary increase is not immediately possible. While a salary increase would be ideal, I understand that there may be constraints or limitations within the company's budget. In such a case, I am open to discussing alternative forms of compensation that may still provide value and meet my financial needs. This could include options such as performance-based bonuses, stock options, additional paid time off, professional development opportunities, or other benefits that align with my career growth and financial goals. I believe in maintaining open and constructive communication during the negotiation process to find a mutually beneficial solution.
**AI:** 10\. Have you considered the potential outcomes and next
steps if your request for a raise is not approved?
**Andrea AI:** Yes, I have considered the potential outcomes and next steps if my request for a raise is not approved. While I am hopeful that my manager will recognize my contributions and grant the raise, I understand that it may not always be possible due to various factors. In such a scenario, I am committed to maintaining a positive attitude and continuing to perform at a high level in my current role.
If my request for a raise is not approved, I would seek feedback from my manager on areas of improvement and opportunities for growth within the company. I would also explore other avenues for professional development, such as attending training sessions or pursuing certifications, to enhance my skill set and increase my value to the company.
Additionally, I would consider discussing the possibility of a performance-based plan or timeline with my manager, where I can work towards specific goals or milestones that would make me eligible for a raise in the future. This would allow me to have a clear path for advancement and provide an opportunity to revisit the topic of a raise at a later date.
Ultimately, my goal is to continue contributing to the success of the company and growing in my career. If a raise is not immediately possible, I am committed to exploring alternative paths for growth and advancement within the company while keeping an open mind about potential opportunities outside the organization as well.
这是 AI 的最终建议。
**AI**: Based on your responses, here are the exact steps you can
take to address your situation:
1\. Schedule a meeting with your manager: Request a
meeting with your manager to discuss your performance,
contributions, and the possibility of a raise.
2\. Present your case: During the meeting, present a
well-structured argument that highlights your consistent performance,
additional responsibilities, process improvements,
market value research, financial impact, and commitment
to professional development. Clearly state your request for a
raise based on these factors.
3\. Be open to negotiation: Be prepared to negotiate and
consider alternative forms of compensation if a salary increase
is not immediately possible. Discuss options such as
performance-based bonuses, stock options, additional paid time off,
or professional development opportunities.
4\. Seek feedback and growth opportunities: If your request for
a raise is not approved, seek feedback from your manager on areas
of improvement and opportunities for growth within the company.
Explore avenues for professional development to enhance your skills
and increase your value to the company.
5\. Discuss a performance-based plan: Consider discussing the
possibility of a performance-based plan or timeline with your manager,
where you can work towards specific goals or milestones that would make
you eligible for a raise in the future. This provides a clear path
for advancement and an opportunity to revisit the topic of a
raise at a later date.
6\. Maintain a positive attitude and performance: Regardless
of the outcome, maintain a positive attitude and continue to
perform at a high level in your current role. Stay committed to
contributing to the success of the company and explore growth opportunities
within or outside the organization.
Remember, it's important to approach the meeting with professionalism, confidence, and a focus on the value you bring to the company. Good luck with your meeting and your pursuit of a raise!
当然,我的初步提示可能过于笼统,无法生成针对我们具体案例的最终回应,但请考虑以下学习点:
-
你知道应该关注什么,如果你面对那个特定问题(研究市场以查看同行的薪酬,收集你对公司的贡献信息)。
-
你有一个关于软件工程师应该如何回答这些问题的想法。
-
你实际上有一个最终建议,虽然很通用,但是一个很好的起点。
最后,请记住,在这个阶段你可以手动跟进,以帮助 GPT 纠正/改进其最终回应。也许是我,所有这些看起来都像魔法。
结论
翻转互动模式利用 GPT 帮助集中于问题而非答案。这种方法使你能够更深入地理解当前问题,并为 AI 提供一个框架,以提供个性化和准确的回应;基本上,你只需要能够提出你的问题!
总的来说,我发现这种方法非常有用,所以我鼓励你尝试一下,但请注意:将自动化部分仅视为有趣的练习,避免将可能的与经理的对话交给它!
如果你尝试了这个方法,或者有改进的想法,请随时联系我!
一名数据科学家在需求高/供应低的市场中寻找物业的探险
## 数据科学的好处:我如何在都柏林找到新家 ## 如何用旅行推销员问题拯救圣诞节
圣诞老人最佳旅行路线的一个很好的可视化
附录
如果你想直接复制粘贴并尝试的话,下面是完整代码!
使用 Python 类掌握时间序列分析
原文:
towardsdatascience.com/mastering-time-series-analysis-with-python-classes-1a4215e433f8
面向对象编程在时间序列分析中的应用
·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 1 月 10 日
–
图片由Tima Miroshnichenko提供,发布于Pexels
时间序列分析是最常见的数据科学任务之一。它涉及分析按时间顺序排列的数据点的趋势。时间序列数据种类繁多,包括股票市场数据、天气数据、消费者需求数据等。时间序列分析在各个行业中都有应用,这使得它成为数据科学家和数据分析师的必备技能。
时间序列分析涉及许多技术,无法在一篇文章中总结。一些最常见的方法包括通过折线图可视化时间序列数据、构建时间序列预测模型、进行光谱分析以揭示周期性趋势、分析季节性趋势等。
由于时间序列分析涉及多种不同的技术,它自然适合面向对象编程。Python 类使得组织相关时间序列任务的属性和方法变得简单。例如,如果作为数据科学家,你经常进行折线图可视化、季节性分析和时间序列预测,类可以让你轻松组织这些任务的方法和属性。
当面向对象编程做得好的时候,可以提高时间序列实验的可读性、可重用性、可维护性和重复性。由于类是方法和属性的集合,它清楚地指出了哪些函数用于特定目的。这使得修改和维护现有函数变得简单。此外,一旦在你的类中定义了可靠的方法集合,你可以轻松地使用不同的参数、更新的训练数据等重新运行实验,而无需重新编写代码。
在这里,我们将演示如何编写一个类,以组织时间序列分析工作流中的步骤。工作流的每个部分将由一个类方法定义,该方法完成单个任务。我们将学习如何为时间序列可视化、统计测试、数据拆分用于训练和测试、训练预测模型和验证时间序列模型定义类方法。
在这个工作中,我将使用Deepnote编写代码,它是一个协作数据科学笔记本,使得运行可重复的实验变得非常容易。
对于我们的建模工作,我们将使用虚构的Weather Climate Time Series data set,该数据集公开可在 Kaggle 上获取。该数据集可以免费使用、修改和共享,遵循创作共用公共领域许可证 (CC0 1.0)。
读取数据
首先,让我们导航到 Deepnote 并创建一个新项目(如果你还没有账户,可以免费注册)。
我们来创建一个名为‘time_series’的项目,并在该项目中创建一个名为‘time_series_oop’的笔记本。同时,将 DailyDelhiClimate.csv 文件拖放到页面左侧面板上“FILES”部分:
作者拍摄的截图
我们从导入 Pandas 库开始:
import pandas as pd
接下来,我们将定义一个类来读取我们的天气数据。我们将把我们的 Python 类命名为 TimeSeriesAnalysis:
class TimeSeriesAnalysis:
def __init__(self, data):
self.df = pd.read_csv(data)
我们可以定义一个类的实例,并通过我们的 TimeSeriesAnalysis 对象访问我们的数据框。让我们显示数据的前五行:
作者创建的嵌入
我们看到我们有一个日期对象和四个浮点列。我们有平均温度、湿度、风速和平均气压。让我们为我们的类添加一个准备时间序列的方法。该方法将接受一个数值列名,并返回一个时间序列,其中数据作为索引,值对应于选定的列:
class TimeSeriesAnalysis:
...
def get_time_series(self, target):
self.df['date'] = pd.to_datetime(self.df['date'])
self.df.set_index('date', inplace=True)
self.ts_df = self.df[target]
现在我们可以定义我们类的一个新实例,并访问我们的时间序列数据:
作者创建的嵌入
生成摘要统计信息
接下来,我们可以添加一个生成时间序列基本统计信息的方法。例如,我们可以定义一个类方法,返回指定列的均值和标准差:
class TimeSeriesAnalysis:
...
def get_summary_stats(self):
print(f"Mean {self.target}: ", self.ts_df.mean())
print(f"Standard Dev. {self.target}: ", self.ts_df.std())
然后,我们可以定义一个新的类实例,在我们的对象实例上调用 get_time_series 方法,并将平均温度列作为输入,生成摘要统计数据:
嵌入由作者创建
我们可以对湿度、风速和平均压力做同样的处理
嵌入由作者创建
可视化时间序列
下一步我们可以定义一个方法来生成一些可视化。对于我们的可视化,我们需要导入 Seaborn、Matplotlib 和 statsmodels 包:
import seaborn as sns
import matplotlib.pyplot as plt
import statsmodels.api as sm
让我们为平均温度定义一个新的类实例,创建一个折线图、直方图,并进行季节性分解:
class TimeSeriesAnalysis:
...
def visualize(self, line_plot, histogram, decompose):
sns.set()
if line_plot:
plt.plot(self.ts_df)
plt.title(f"Daily {self.target}")
plt.xticks(rotation=45)
plt.show()
if histogram:
self.ts_df.hist(bins=100)
plt.title(f"Histogram of {self.target}")
plt.show()
if decompose:
decomposition = sm.tsa.seasonal_decompose(self.ts_df, model='additive', period =180)
fig = decomposition.plot()
plt.show()
让我们让我们的方法提供生成目标值的直方图、时间序列折线图和时间序列分解的选项:
嵌入由作者创建
平稳性检验
Dickey-Fuller 调整器
我们可以添加的另一个方法是使用 Dickey-Fuller 检验进行平稳性测试。平稳性是指时间序列的均值和方差随时间不会改变。此外,如果时间序列是平稳的,它就没有任何趋势。通过检查我们的图表,我们可以看到天气数据是非平稳的,因为存在明显的季节性趋势。我们将使用 Dickey-Fuller 检验来检查平稳性。Dickey-Fuller 检验具有以下假设:
原假设:时间序列是非平稳的。
备择假设:时间序列是平稳的。
我们以以下方式解释结果:
如果检验统计量 < 临界值,我们拒绝原假设
如果检验统计量 > 临界值,我们未能拒绝原假设
检验结果将具有 1%、5% 和 10% 显著性水平的临界值。结果还将包括检验统计量。让我们定义一个方法来运行这个测试:
class TimeSeriesAnalysis:
...
def stationarity_test(self):
adft = adfuller(self.ts_df,autolag="AIC")
output_df = pd.DataFrame({"Values":[adft[0],adft[1],adft[2],adft[3], adft[4]['1%'], adft[4]['5%'], adft[4]['10%']] , "Metric":["Test Statistics","p-value","No. of lags used","Number of observations used",
"critical value (1%)", "critical value (5%)", "critical value (10%)"]})
self.adf_results = output_df
在我们的笔记本的顶部,让我们从 statsmodels 包中导入 adfuller 方法:
from statsmodels.tsa.stattools import adfuller
然后,我们可以在我们的气候对象上调用 stationarity_test 方法并访问测试结果:
嵌入由作者创建
我们看到在每种情况下,临界值都小于检验统计量,这是可以预期的。这意味着我们的数据是非平稳的。
Kwiatkowski — Phillips — Schmidt — Shin (KPSS)
原假设:时间序列是平稳的。
备择假设:时间序列是非平稳的。
类似于 ADF 测试,我们以以下方式解释结果:
如果检验统计量 < 临界值,我们拒绝原假设
如果检验统计量 > 临界值,我们未能拒绝原假设
另一个平稳性测试是 Kwiatkowski — Phillips — Schmidt — Shin 测试 (KPSS)。该测试的原假设是时间序列是平稳的。ADF 和 KPSS 之间的区别在于 ADF 测试平稳性,而 KPSS 测试非平稳性。如果 ADF 测试结果未能拒绝原假设,则应使用 KPSS 来确认时间序列是非平稳的。我们扩展我们的平稳性测试方法,使其同时提供 ADF 和 KPSS 的选项。让我们从 sstats
模块导入 KPSS 方法:
from statsmodels.tsa.stattools import adfuller, kpss
然后我们可以扩展我们平稳性测试方法的定义,以包括 KPSS:
class TimeSeriesAnalysis:
...
def stationarity_test(self):
adft = adfuller(self.ts_df,autolag="AIC")
kpsst = kpss(self.ts_df,autolag="AIC")
adf_results = pd.DataFrame({"Values":[adft[0],adft[1],adft[2],adft[3], adft[4]['1%'], adft[4]['5%'], adft[4]['10%']] , "Metric":["Test Statistics","p-value","No. of lags used","Number of observations used",
"critical value (1%)", "critical value (5%)", "critical value (10%)"]})
kpss_results = pd.DataFrame({"Values":[kpsst[0],kpsst[1],kpsst[2], kpsst[3]['1%'], kpsst[3]['5%'], kpsst[3]['10%']] , "Metric":["Test Statistics","p-value","No. of lags used",
"critical value (1%)", "critical value (5%)", "critical value (10%)"]})
self.adf_results = adf_results
self.kpss_resulta = kpss_results
然后我们可以在我们的气候对象上调用 stationarity_test
方法,并访问 KPSS 测试结果:
作者创建的嵌入
我们看到测试统计量小于临界值,这意味着我们拒绝原假设。原假设声明时间序列是平稳的。这意味着我们有证据支持时间序列是非平稳的。
我们可以定义一个执行 ADF 和 KPSS 测试并打印结果的方法:
class TimeSeriesAnalysis:
...
def stationarity_test(self):
adft = adfuller(self.ts_df,autolag="AIC")
kpsst = kpss(self.ts_df)
adf_results = pd.DataFrame({"Values":[adft[0],adft[1],adft[2],adft[3], adft[4]['1%'], adft[4]['5%'], adft[4]['10%']] , "Metric":["Test Statistics","p-value","No. of lags used","Number of observations used",
"critical value (1%)", "critical value (5%)", "critical value (10%)"]})
kpss_results = pd.DataFrame({"Values":[kpsst[0],kpsst[1],kpsst[2], kpsst[3]['1%'], kpsst[3]['5%'], kpsst[3]['10%']] , "Metric":["Test Statistics","p-value","No. of lags used",
"critical value (1%)", "critical value (5%)", "critical value (10%)"]})
self.adf_results = adf_results
self.kpss_results = kpss_results
print(self.adf_results)
print(self.kpss_results)
self.adf_status = adf_results['Values'].iloc[1] > adf_results['Values'].iloc[4]
self.kpss_status = kpss_results['Values'].iloc[1] < kpss_results['Values'].iloc[3]
print("ADF Results: ", self.adf_status)
print("KPSS Results: " ,self.kpss_status)
结果如下:
作者创建的嵌入
最好使用两种测试来确认时间序列是否平稳。这两种测试的结果强烈表明时间序列是非平稳的。
有趣的是,最常见的时间序列预测方法,如 ARIMA 和 SARIMA 可以处理非平稳数据。通常建议在拟合 ARIMA 模型之前进行差分。Auto ARIMA 是一个 Python 包,它允许你自动搜索最佳差分参数。这很方便,因为不需要进行手动测试来找到最佳的差分阶数。
划分训练和测试数据
在准备训练预测模型的数据时,重要的是将数据划分为训练集和测试集。这有助于防止模型过拟合数据,从而导致泛化能力差。我们将训练集定义为 2016 年 7 月之前的所有数据,将测试集定义为 2016 年 7 月之后的所有数据点:
def train_test_split(self):
self.y_train = self.ts_df[self.ts_df.index <= pd.to_datetime('2016-07')]
self.y_test = self.ts_df[self.ts_df.index > pd.to_datetime('2016-07')]
自动 ARIMA 模型
要使用 Auto ARIMA,首先让我们安装 pdarima
包:
!pip install pmdarima
接下来,让我们从 pdarima
导入 auto arima
:
from pmdarima.arima import auto_arima
然后我们可以定义我们的拟合方法,用于将 ARIMA 模型拟合到训练数据中:
def fit(self):
self.y_train.fillna(0,inplace=True)
model = auto_arima(self.y_train, trace=True, error_action='ignore', suppress_warnings=True, stationary=True)
model.fit(self.y_train)
forecast = model.predict(n_periods=len(self.ts_df))
self.forecast = pd.DataFrame(forecast,index = self.ts_df.index,columns=['Prediction'])
self.ts_df = pd.DataFrame(self.ts_df, index = self.forecast.index)
self.y_train = self.ts_df[self.ts_df.index < pd.to_datetime('2016-07')]
self.y_test = self.ts_df[self.ts_df.index > pd.to_datetime('2016-07')]
self.forecast = self.forecast[self.forecast.index > pd.to_datetime('2016-07')]
我们还将定义一个验证方法,该方法展示性能并可视化模型预测。我们将使用平均绝对误差来评估性能:
def validate(self):
plt.plot(self.y_train, label='Train')
plt.plot(self.y_test, label='Test')
plt.plot(self.forecast, label='Prediction')
mae = np.round(mean_absolute_error(self.y_test, self.forecast), 2)
plt.title(f'{self.target} Prediction; MAE: {mae}')
plt.xlabel('Date')
plt.ylabel(f'{self.target}')
plt.xticks(rotation=45)
plt.legend(loc='upper left', fontsize=8)
plt.show()
完整的类如下所示:
作者创建的嵌入
现在,如果我们定义一个新的实例,对其进行拟合并验证模型,我们得到:
作者创建的嵌入
本文中的代码可以在 GitHub 上找到。
结论
在这篇文章中,我们讨论了如何编写一个类来组织时间序列分析工作流程的步骤。首先,我们定义了一个方法,使我们能够读取和显示数据。然后,我们定义了一个方法来计算时间序列的均值和标准差。接着,我们定义了一个方法,使我们能够可视化时间序列数据的折线图、时间序列值的直方图以及数据的季节性分解。随后,我们编写了一个类方法,使我们能够执行平稳性统计检验。我们使用了 ADF 检验来测试平稳性,并使用了 KPSS 检验来测试非平稳性。最后,我们将一个 Auto ARIMA 模型拟合到训练数据中,验证了我们的模型,并为预测生成了可视化图。我希望你觉得这篇文章有用。我鼓励你尝试将这些方法应用到你的时间序列分析项目中!