时间序列机器学习回归框架
构建时间序列预测管道来预测每周销售交易
Most commonly, a time series is a sequence taken at successive equally spaced points in time. Thus it is a sequence of discrete-time data. Photo by Justin Campbell on Unsplash
介绍
Fig.1) Let’s borrow the concept of time from Entropy & thermodynamics: Entropy is the only quantity in the physical sciences that seems to imply a particular direction of progress, sometimes called an arrow of time. As time progresses, the second law of thermodynamics states that the entropy of an isolated system never decreases in large systems over significant periods of time. Can we consider our Universe as an isolated large system? Then what?
时间!宇宙中最具挑战性的概念之一。我研究物理学多年,看到大多数杰出的科学家都在努力处理时间的概念。在机器学习中,即使我们远离那些复杂的物理理论,在这些理论中,对时间概念的共同理解会改变,时间的存在和 ML 问题中的观察序列会使问题变得更加复杂。
时间序列的机器学习;
Fig.2) In time series forecasting, we use historical data to forecast the future. George Santayana: Those Who Do Not Learn History Are Doomed To Repeat It. The right figure is taken from https://www.kdnuggets.com/2018/02/cartoon-valentine-machine-learning.html
时间序列是按时间顺序进行的一系列观察。时间序列预测包括采用模型,然后根据历史数据进行拟合,然后用它们来预测未来的观测值。因此,例如,测量的分钟(秒)、天(秒)、月(秒)、前(秒)被用作预测
Fig.3) Transform Time Series to Supervised Machine Learning.
接下来的分钟、天、月。被认为是在时间(序列)上向后移动数据的步骤,称为滞后时间或滞后。因此,通过添加测量的滞后作为监督 ML 的输入,可以将时间序列问题转化为监督 ML。见图 3 右侧。一般来说,探索滞后的数量作为一个超参数。
Fig.4) Transform the time series to supervised machine learning by adding lags. Lags are basically the shift of the data one step or more backward in the time.
时间序列的交叉验证
时间序列的交叉验证不同于不涉及时间或序列的机器学习问题。在没有时间的情况下,我们选择一个随机的数据子集作为验证集来估计测量的准确性。在时间序列中,我们经常预测未来的某个值。因此,验证数据总是必须在训练数据的之后出现。有两种模式滑动窗口和前向链接验证方法,可用于时间序列 CV。
Fig. 5) Basically, there are two kinds of cross-validation for the time series sliding window and forward chaining. In this post, we will consider forward chaining cross-validation method
图 5 顶部显示了滑动窗口方法。对于这种方法,我们在 n 个数据点上训练,并在接下来的 n 个数据点上验证预测,为下一步及时滑动 2n 训练/验证窗口。图 5 底部显示了正向链接方法。对于这种方法,我们在最后 n 个数据点上训练,并在接下来的 m 个数据点上验证预测,在时间上滑动 n+m 训练/验证窗口。这样,我们就可以估计我们模型的参数了。为了测试模型的有效性,我们可能会在时间序列的末尾使用一个数据块,该数据块是为用学习到的参数测试模型而保留的。
Fig.6) Forward chaining cross-validation.
图六。显示了正向链接 CV 的工作方式。在这里,有一个滞后。因此,我们从第一秒到第三秒/分钟/小时/天等来训练模型。然后验证第四个,以此类推。既然现在我们已经熟悉了 TS 问题,让我们选择一个时间序列问题并建立一个预测模型。
预测每周销售交易
假设一家商店的经理要求我们建立一个 ML 模型来预测下周的销售额。该模型必须每周日运行,预测结果必须在每周一上午报告。然后,经理可以决定一周的订单数量。经理给我们提供了 52 周 811 产品的销售数据。销售数据可以在 UCI 仓库 或 kaggle 中找到。
我们来看数据。
许多数据科学家可能会为每种产品创建一个模型来预测销售数量。虽然这可以很好地工作,但我们可能会有问题,因为每个模型只有 52 个数据点,这真的很低!尽管这种方法是可行的,但它可能不是最佳解决方案。此外,如果两种或更多产品的销售数量之间存在交互,我们可能会因为为每种产品构建一个模型而错过它们的交互。因此,在这篇文章中,我们将探讨如何建立一个多时间序列预测模型。
数据准备
原始数据有一个产品代码列和 52 周销售额列。首先,我们将通过融合周数据来创建一个新的数据框架。因此,新的数据框架有三列,产品代码、周和销售额。此外,“W”和“P”分别从星期和产品中删除。那么,让我们看看新数据帧的头部和尾部
为了熟悉数据集,销售分布绘制在图 7 中。可以看出,有大量产品的销售额很低,数据也向左倾斜。这个问题对建模的影响将在后面讨论。
Fig. 7) Sales distribution. There are many product sales items with very low sales.
基础特征工程
因为这篇文章的目标不是 TS 的特性工程,我们将尽量保持这部分简单。让我们创建两个通常用于时间序列的特征。时间上后退一步,1-lag(shift =1)和一周前(W 1)的购买数量与其前一周的购买数量之差,意味着,两周前(W2)。此后,由于 lag 和 diff 导致数据集中的值为空,参见图 4,我们将其删除。因此,当我们查看数据帧的头部时,它从 week = 2 开始。
“ToSupervised”和“ToSupervisedDiff”类,代码 1 和代码 2,如编码部分所示,用于通过简单的管道获得新的数据帧:
steps = [('1_step',
ToSupervised('Sales','Product_Code',1)),
('1_step_diff',
ToSupervisedDiff('1_Week_Ago_Sales',
'Product_Code',1,dropna=True))]super_1 = Pipeline(steps).fit_transform(df)
现在,数据有了适当的形状,可以在受监督的 ML 中使用。
正向链接交叉验证:
另一个问题是,当我们处理时间序列时,我们必须处理时间序列的 CV。我们选择前向链接进行模型验证。为了避免在很少的几周内有一个非常好的模型,我们将使用从 40 到 52 的每一周,每次重复一个过程,并计算分数。因此,这个模式下的 k 倍代码可以在 C. 3 中找到。
kf = Kfold_time(target='Sales',date_col = 'Week',
date_init=40, date_final=52)
因为这篇文章只是一个演示,所以我不打算分离一个测试数据集。在实际项目中,总是保留一些时间段作为测试数据集,以根据看不见的数据评估模型。
公制的
由于问题是回归,有几个众所周知的指标来评估模型,如均方误差(MSE),平均绝对误差(MAE),均方根误差(RMSE),均方根对数误差(RMSLE),R 平方,等等。这些度量标准中的每一个都有自己的用例,它们以不同的方式惩罚错误,但它们之间也有一些相似之处。在本文中,我们选择 RMSLE 来评估这个模型。
基线:
通常,当我们构建一个模型时,我们可能会提出一个非常简单的假设,认为使用 ML 可以改进它。在这里,让我们假设每种产品的数量在本周售出,下周也是如此。这意味着,如果产品-1 在第 1 周销售了 10 次,那么它在第 2 周的销售数字也将是相同的。这通常是一个不错的假设。所以,让我们把这个假设当作我们的基线模型。
基线模型用 C. 5 编码,让我们看看基线模型是如何工作的
base_model = BaseEstimator('1_Week_Ago_Sales')
errors = []
for indx,fold in enumerate(kf.split(super_1)):
X_train, X_test, y_train, y_test = fold
error = base_model.score(X_test,y_test,rmsle)
errors.append(error)
print("Fold: {}, Error: {:.3f}".format(indx,error))
print('Total Error {:.3f}'.format(np.mean(errors)))
折:0,误差:0.520
折:1,误差:0.517
折:2,误差:0.510
折:3,误差:0.508
折:4,误差:0.534
折:5,误差:0.523
折:6,误差:0.500
折:7,误差:0.491
折:8,误差:0.506 【T8
这里,折叠 0 到 11 表示周= 40 到周= 52。基线模型在这 12 周内的 RMSLE 平均值为 0.51。这可以被认为是一个很大的错误,这可能是由于图 7 所示的大量商品的销售量很少造成的
机器学习模型:
现在,我们将应用 ML 来改进基线预测。让我们定义一个时间序列回归器类 C. 5,它与我们的时间序列交叉验证一起工作。该类获取 cv 和模型,并返回模型预测及其得分。有很多种最大似然算法可以用作估计器。这里,我们选择一个随机的森林。简单地说,RF 可以被认为是装袋和在决策树顶部随机选择特征列的组合。因此,它减少了决策树模型预测的方差。因此,它通常比单个树具有更好的性能,但比旨在减少决策树模型的偏差误差的集成方法具有更弱的性能。
model = RandomForestRegressor(n_estimators=1000,
n_jobs=-1,
random_state=0)steps_1 = [('1_step',
ToSupervised('Sales','Product_Code',1)),
('1_step_diff',
ToSupervisedDiff('1_Week_Ago_Sales',
'Product_Code',1,dropna=True)),
('predic_1',
TimeSeriesRegressor(model=model,cv=kf))]super_1_p = Pipeline(steps_1).fit(df)
Model_1_Error = super_1_p.score(df)
我们得到了
倍:0,误差:0.4624
倍:1,误差:0.4596
倍:2,误差:0.4617
倍:3,误差:0.4666
倍:4,误差:0.4712
倍:5,误差:0.4310
倍:6,误差:0.4718
倍:7,误差:0.4494
倍:8,误差:
似乎模型起作用了,误差减小了。让我们添加更多的滞后,并再次评估模型。因为我们构建了管道,所以添加更多的 lag 会非常简单。
steps_3 = [('1_step',
ToSupervised('Sales','Product_Code',3)),
('1_step_diff',
ToSupervisedDiff('1_Week_Ago_Sales','Product_Code',1)),
('2_step_diff',
ToSupervisedDiff('2_Week_Ago_Sales','Product_Code',1)),
('3_step_diff',
ToSupervisedDiff('3_Week_Ago_Sales',
'Product_Code',1,dropna=True)),
('predic_3',
TimeSeriesRegressor(model=model,cv=kf,scoring=rmsle))]
super_3_p = Pipeline(steps_3).fit(df)
倍:0,误差:0.4312
倍:1,误差:0.4385
倍:2,误差:0.4274
倍:3,误差:0.4194
倍:4,误差:0.4479
倍:5,误差:0.4070
倍:6,误差:0.4395
倍:7,误差:0.4333【T15
似乎预测的误差再次减小,并且模型学习得更多。我们可以继续添加滞后,看看模型的性能如何变化;然而,我们将推迟这个过程,直到我们使用 LGBM 作为一个估计。
统计转换:
销售的分布,图 7 显示数据向低销售数字或左边倾斜。通常,对数变换在应用于偏斜分布时很有用,因为它们倾向于扩展较低幅度范围内的值,并倾向于压缩或减少较高幅度范围内的值。当我们进行统计变换时,模型的可解释性会发生变化,因为系数不再告诉我们原始特征,而是变换后的特征。因此,当我们对销售数字应用 np.log1p 来转换其分布以使其更接近正态分布时,我们也对预测结果应用 np.expm1,参见 C. 6,TimeSeriesRegressorLog。现在,我们用提到的变换重复计算
steps_3_log = [('1_step',
ToSupervised('Sales','Product_Code',3)),
('1_step_diff',
ToSupervisedDiff('1_Week_Ago_Sales',
'Product_Code',1)),
('2_step_diff',
ToSupervisedDiff('2_Week_Ago_Sales',
'Product_Code',1)),
('3_step_diff',
ToSupervisedDiff('3_Week_Ago_Sales',
'Product_Code',1,dropna=True)),
('predic_3',
TimeSeriesRegressorLog(model=model,
cv=kf,scoring=rmsle))]
super_3_p_log = Pipeline(steps_3_log).fit(df)
所以我们有
倍:0,误差:0.4168
倍:1,误差:0.4221
倍:2,误差:0.4125
倍:3,误差:0.4035
倍:4,误差:0.4332
倍:5,误差:0.3977
倍:6,误差:0.4263
倍:7,误差:0.4122【T33
这表明模型的性能提高了,误差又减小了。
套装 ML:
现在,是时候使用更强的 ML 估计量来改进预测了。我们选择 LightGBM 作为新的估计器。所以让我们重复计算
model_lgb = LGBMRegressor(n_estimators=1000, learning_rate=0.01)steps_3_log_lgbm = [('1_step',
ToSupervised('Sales','Product_Code',3)),
('1_step_diff',
ToSupervisedDiff('1_Week_Ago_Sales',
'Product_Code',1)),
('2_step_diff',
ToSupervisedDiff('2_Week_Ago_Sales',
'Product_Code',1)),
('3_step_diff',
ToSupervisedDiff('3_Week_Ago_Sales',
'Product_Code',1,
dropna=True)),
('predic_3',
TimeSeriesRegressorLog(model=model_lgb,
cv=kf,scoring=rmsle))] super_3_p_log_lgbm = Pipeline(steps_3_log_lgbm).fit(df)
然后,
倍:0,误差:0.4081
倍:1,误差:0.3980
倍:2,误差:0.3953
倍:3,误差:0.3949
倍:4,误差:0.4202
倍:5,误差:0.3768
倍:6,误差:0.4039
倍:7,误差:0.3868 【T43
我们再次成功地改进了预测。
调整步骤数:
在这一部分,我们将调整步数(滞后/差异)。在使用 LGBM 作为回归变量后,我有意推迟了本节的步骤,因为它比 RF 更快。图 8 清楚地显示,通过向模型添加更多的步骤,误差减小;然而,正如我们预期的那样,在超过步长= 14 左右的阈值后,增加更多的步长不会显著降低误差。您可能有兴趣定义一个错误阈值来停止这个过程。剩余的计算选择步长= 20。请检查代码 C 7。a 和 B 用于调谐。
model_lgbm = LGBMRegressor(n_estimators=1000, learning_rate=0.01)list_scores2 = stepsTune(df,TimeSeriesRegressorLog(model=model_lgbm,
scoring=rmsle,cv=kf,verbosity=False),20)
Fig 8) Tuning the number of lags/diff is shown. The x-axis shows the RMSLE error as a function of the steps (number of lags/diffs). The model improves by adding more steps; however, steps more than 14 do not improve the model significantly.
调整超参数:
在这一部分,我们将实现网格搜索方法,我们可以通过管道应用它,参见代码 8 A 和 B. C.8.A 代码是从 Sklearn 库借来的。这一部分的目的不是构建一个完全调优的模型。我们试图展示工作流程。稍加调整后,误差变为
RMSLE= 0.3868
对于这两个超参数{‘learning_rate’: 0.005,’ n_estimators’: 1500}。
params = {'n_estimators':[100,500,1000,1500,2000],
'learning_rate':[0.005,.01,.1]}steps_20 = getDataFramePipeline(20)
super_20 = Pipeline(steps_20).fit_transform(df)model_lgbm2 = LGBMRegressor(random_state=0)tune_lgbm =TimeSeriesGridSearch(model = model_lgbm2, cv = kf,
param_grid=params,verbosity=False,scoring=rmsle)
当最佳调整超参数处于调整参数的边缘时,意味着我们必须重新考虑超参数的范围并重新计算模型,尽管在本文中我们不会这样做。
预测与实际销售
图九。显示第 52 周的预测值与销售额。可以看出,该模型对于高达 15 的销售数字工作良好;然而,它对 30 左右的销量预测很差。正如我们在图 7 中所讨论的,我们可能会为不同的销售范围建立不同的模型来克服这个问题,并拥有一个更强大的预测模型,尽管进一步的建模超出了本文的范围,并且本文已经很长了。
Fig. 9) Prediction of sales vs. real sales number. It is seen that the model works properly for the low number of sales (less than 15); however, it does not work well for a large number of sales. Therefore, this might be a good motivation to build two models for low and high sales items.
最后,图 10 显示了我们预测销售的所有尝试。我们从一个非常简单的假设作为基线开始,并试图通过使用不同的 lags/diff、统计转换和应用不同的机器学习算法来改进预测。基线误差为 0.516,调整后的模型误差为 0.3868,这意味着误差减少了 25%。
Fig. 10) Our different models score are shown. We could reduce the error of the baseline by 25%.
仍然有许多方法来改进所提出的模型,例如,适当地将产品作为分类变量来处理,更广泛的特征工程,调整超参数,使用各种机器学习算法以及混合和堆叠。
结论:
我们建立了一个时间序列预测管道来预测每周的销售交易。我们从一个简单的逻辑假设作为基线模型开始;然后,我们可以通过构建一个包括基本特征工程、统计转换和应用随机森林和 LGBM 并最终对其进行调优的管道,将基线误差降低 25%。此外,我们还讨论了不同的时间序列交叉验证方法。此外,我们展示了如何使用 Sklearn 基类来构建管道。
编码:
这篇帖子的完整代码可以在我的 GitHub 上找到。
代码 1。
class ToSupervised(base.BaseEstimator,base.TransformerMixin):
def __init__(self,col,groupCol,numLags,dropna=False):
self.col = col
self.groupCol = groupCol
self.numLags = numLags
self.dropna = dropna
def fit(self,X,y=None):
self.X = X
return self
def transform(self,X): tmp = self.X.copy()
for i in range(1,self.numLags+1):
tmp[str(i)+'_Week_Ago'+"_"+self.col] =
tmp.groupby([self.groupCol])[self.col].shift(i)
if self.dropna:
tmp = tmp.dropna()
tmp = tmp.reset_index(drop=True)
return tmp
代码 2。
class ToSupervisedDiff(base.BaseEstimator,base.TransformerMixin):
def __init__(self,col,groupCol,numLags,dropna=False):
self.col = col
self.groupCol = groupCol
self.numLags = numLags
self.dropna = dropna
def fit(self,X,y=None):
self.X = X
return self
def transform(self,X):
tmp = self.X.copy()
for i in range(1,self.numLags+1):
tmp[str(i)+'_Week_Ago_Diff_'+"_"+self.col] =
tmp.groupby([self.groupCol])[self.col].diff(i)
if self.dropna:
tmp = tmp.dropna()
tmp = tmp.reset_index(drop=True)
return tmp
代码 3。
from itertools import chain
class Kfold_time(object):
def __init__(self,**options):
self.target = options.pop('target', None)
self.date_col = options.pop('date_col', None)
self.date_init = options.pop('date_init', None)
self.date_final = options.pop('date_final', None) if options:
raise TypeError("Invalid parameters passed: %s" %
str(options))
if ((self.target==None )|(self.date_col==None )|
(self.date_init==None )|(self.date_final==None )):
raise TypeError("Incomplete inputs")
def _train_test_split_time(self,X):
n_arrays = len(X)
if n_arrays == 0:
raise ValueError("At least one array required as input") for i in range(self.date_init,self.date_final): train = X[X[self.date_col] < i]
val = X[X[self.date_col] == i] X_train, X_test = train.drop([self.target], axis=1),
val.drop([self.target], axis=1) y_train, y_test = train[self.target].values,
val[self.target].values yield X_train, X_test, y_train, y_test def split(self,X):
cv_t = self._train_test_split_time(X)
return chain(cv_t)
代码 4。
class BaseEstimator(base.BaseEstimator, base.RegressorMixin):
def __init__(self, predCol):
"""
As a base model we assume the number of sales
last week and this week are the same
Input:
predCol: l-week ago sales
"""
self.predCol = predCol def fit(self, X, y):
return self def predict(self, X):
prediction = X[self.predCol].values
return prediction def score(self, X, y,scoring):
prediction = self.predict(X)
error =scoring(y, prediction) return error
代码 5。
class TimeSeriesRegressor(base.BaseEstimator, base.RegressorMixin):
def __init__(self,model,cv,scoring,verbosity=True):
self.model = model
self.cv = cv
self.verbosity = verbosity
self.scoring = scoring
def fit(self,X,y=None):
return self
def predict(self,X=None):
pred = {}
for indx,fold in enumerate(self.cv.split(X)): X_train, X_test, y_train, y_test = fold
self.model.fit(X_train, y_train)
pred[str(indx)+'_fold'] = self.model.predict(X_test)
prediction = pd.DataFrame(pred)
return prediction def score(self,X,y=None): errors = []
for indx,fold in enumerate(self.cv.split(X)): X_train, X_test, y_train, y_test = fold
self.model.fit(X_train, y_train)
prediction = self.model.predict(X_test)
error = self.scoring(y_test, prediction)
errors.append(error) if self.verbosity:
print("Fold: {}, Error: {:.4f}".format(indx,error)) if self.verbosity:
print('Total Error {:.4f}'.format(np.mean(errors))) return errors
代码 6。
class TimeSeriesRegressorLog(base.BaseEstimator,
base.RegressorMixin):
def __init__(self,model,cv,scoring,verbosity=True):
self.model = model
self.cv = cv
self.verbosity = verbosity
self.scoring = scoring
def fit(self,X,y=None):
return self
def predict(self,X=None):
pred = {}
for indx,fold in enumerate(self.cv.split(X)): X_train, X_test, y_train, y_test = fold
self.model.fit(X_train, y_train)
pred[str(indx)+'_fold'] = self.model.predict(X_test)
prediction = pd.DataFrame(pred)
return prediction def score(self,X,y=None):#**options): errors = []
for indx,fold in enumerate(self.cv.split(X)): X_train, X_test, y_train, y_test = fold
self.model.fit(X_train, np.log1p(y_train))
prediction = np.expm1(self.model.predict(X_test))
error = self.scoring(y_test, prediction)
errors.append(error) if self.verbosity:
print("Fold: {}, Error: {:.4f}".format(indx,error)) if self.verbosity:
print('Total Error {:.4f}'.format(np.mean(errors))) return errors
代码 7。
答:
def getDataFramePipeline(i):
steps = [(str(i)+'_step',
ToSupervised('Sales','Product_Code',i))]
for j in range(1,i+1):
if i==j: pp = (str(j)+'_step_diff',
ToSupervisedDiff(str(i)+'_Week_Ago_Sales',
'Product_Code',1,dropna=True)) steps.append(pp)
else: pp = (str(j)+'_step_diff',
ToSupervisedDiff(str(i)+'_Week_Ago_Sales',
'Product_Code',1)) steps.append(pp)
return steps
乙:
from tqdm import tqdm
def stepsTune(X,model,num_steps,init=1):
scores = []
for i in tqdm(range(init,num_steps+1)):
steps = []
steps.extend(getDataFramePipeline(i))
steps.append(('predic_1',model))
super_ = Pipeline(steps).fit(X)
score_ = np.mean(super_.score(X))
scores.append((i,score_))
return scores
代码 8。
答:
from collections.abc import Mapping, Sequence, Iterable
from itertools import product
from functools import partial, reduce
import operatorclass TimeGridBasic(base.BaseEstimator, base.RegressorMixin):
def __init__(self,param_grid):
if not isinstance(param_grid, (Mapping, Iterable)):
raise TypeError('Parameter grid is not a dict or '
'a list ({!r})'.format(param_grid)) if isinstance(param_grid, Mapping):
# wrap dictionary in a singleton list to support
either dict
# or list of dicts
param_grid = [param_grid] if isinstance(param_grid, Mapping):
# wrap dictionary in a singleton list to support
either dict
# or list of dicts
param_grid = [param_grid] # check if all entries are dictionaries of lists
for grid in param_grid:
if not isinstance(grid, dict):
raise TypeError('Parameter grid is not a '
'dict ({!r})'.format(grid))
for key in grid:
if not isinstance(grid[key], Iterable):
raise TypeError('Parameter grid value is not
iterable '
'(key={!r}, value={!r})'
.format(key, grid[key])) self.param_grid = param_grid
def __iter__(self):
"""Iterate over the points in the grid.
Returns
-------
params : iterator over dict of string to any
Yields dictionaries mapping each estimator parameter to
one of its
allowed values.
"""
for p in self.param_grid:
# Always sort the keys of a dictionary, for
reproducibility
items = sorted(p.items())
if not items:
yield {}
else:
keys, values = zip(*items)
for v in product(*values):
params = dict(zip(keys, v))
yield params
乙:
class TimeSeriesGridSearch(TimeGridBasic,base.BaseEstimator,
base.RegressorMixin):
def __init__(self,**options):
self.model = options.pop('model', None)
self.cv = options.pop('cv', None)
self.verbosity = options.pop('verbosity', False)
self.scoring = options.pop('scoring', None)
param_grid = options.pop('param_grid', None)
self.param_grid = TimeGridBasic(param_grid)
if options:
raise TypeError("Invalid parameters passed: %s" %
str(options)) if ((self.model==None )| (self.cv==None)):
raise TypeError("Incomplete inputs")
def fit(self,X,y=None):
self.X = X
return self def _get_score(self,param): errors = []
for indx,fold in enumerate(self.cv.split(self.X)): X_train, X_test, y_train, y_test = fold
self.model.set_params(**param).fit(X_train, y_train)
prediction = self.model.predict(X_test)
error = self.scoring(y_test, prediction)
errors.append(error) if self.verbosity:
print("Fold: {}, Error: {:.4f}".format(indx,error)) if self.verbosity:
print('Total Error {:.4f}'.format(np.mean(errors)))
return errors def score(self): errors=[]
get_param = []
for param in self.param_grid:
if self.verbosity:
print(param)
errors.append(np.mean(self._get_score(param)))
get_param.append(param) self.sorted_errors,self.sorted_params =
(list(t) for t in zip(*sorted(zip(errors,get_param))))
return self.sorted_errors,self.sorted_params
def best_estimator(self,verbosity=False): if verbosity:
print('error: {:.4f} \n'.format(self.sorted_errors[0]))
print('Best params:')
print(self.sorted_params[0]) return self.sorted_params[0]
价格异常检测的时间序列
Photo credit: Pixabay
异常检测会检测数据中与其余数据不匹配的数据点。
也称为异常检测,异常检测是一个数据挖掘过程,用于确定在数据集中发现的异常类型,并确定其发生的详细信息。在当今世界,自动异常检测至关重要,因为海量数据使得无法手动标记异常值。自动异常检测有着广泛的应用,如欺诈检测、系统健康监控、故障检测以及传感器网络中的事件检测系统等。
但我想对酒店房价进行异常检测。原因有些自私。
你有过这样的经历吗,比如说,你经常去某个目的地出差,并且总是住在同一家酒店。虽然大多数情况下,房价几乎是相似的,但偶尔对于同一家酒店,同一种房间类型,房价高得令人无法接受,你必须换到另一家酒店,因为你的差旅补助不包括该房价。我已经经历过几次了,这让我想到,如果我们可以创建一个模型来自动检测这种价格异常会怎么样?
当然,有些情况下,一些异常现象一生只会发生一次,我们已经提前知道了它们,并且很可能在未来几年内不会在同一时间发生,例如 2019 年 2 月 2 日至 2 月 4 日亚特兰大荒谬的酒店价格。
Figure 1
在这篇文章中,我将探索不同的异常检测技术,我们的目标是用无监督学习来搜索酒店房价时间序列中的异常。我们开始吧!
数据
很难得到数据,我能得到一些,但数据并不完美。
我们将要使用的数据是个性化 Expedia 酒店搜索数据集的子集,可以在这里找到。
我们将对 training.csv 集合的子集进行切片,如下所示:
- 选择一家拥有最多数据点
property_id = 104517
的酒店。 - 选择
visitor_location_country_id = 219
,因为我们从另一个分析中知道国家 id 219 是美国。我们这样做的原因是为了统一price_usd
栏目。因为不同的国家有不同的关于显示税费和费用的惯例,并且该值可以是每晚或整个住宿。我们知道展示给我们游客的价格总是每晚不含税。 - 选择
search_room_count = 1
。 - 选择我们需要的特征:
date_time
、price_usd
、srch_booking_window
、srch_saturday_night_bool
。
expedia = pd.read_csv('expedia_train.csv')
df = expedia.loc[expedia['prop_id'] == 104517]
df = df.loc[df['srch_room_count'] == 1]
df = df.loc[df['visitor_location_country_id'] == 219]
df = df[['date_time', 'price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
经过切片和切块后,这是我们将要处理的数据:
df.info()
Figure 2
df['price_usd'].describe()
在这一点上,我们已经发现了一个极端的异常情况,即最高价格为 5584 美元。
如果一个单独的数据实例相对于其余的数据可以被认为是异常的,我们称之为*(例如,大额交易的采购)。我们可以回去查看日志,看看是关于什么的。经过一点点调查,我猜想这要么是一个错误,要么是用户偶然搜索了一个总统套房,并没有打算预订或查看。为了找到更多不极端的异常,我决定去掉这个。*
*expedia.loc[(expedia['price_usd'] == 5584) & (expedia['visitor_location_country_id'] == 219)]*
Figure 3
*df = df.loc[df['price_usd'] < 5584]*
在这一点上,我相信您已经发现我们遗漏了一些东西,也就是说,我们不知道用户搜索的房间类型,标准房的价格可能与海景特大床房的价格相差很大。记住这一点,为了演示的目的,我们必须继续。
时间序列可视化
*df.plot(x='date_time', y='price_usd', figsize=(12,6))
plt.xlabel('Date time')
plt.ylabel('Price in USD')
plt.title('Time Series of room price by date time of search');*
Figure 4
*a = df.loc[df['srch_saturday_night_bool'] == 0, 'price_usd']
b = df.loc[df['srch_saturday_night_bool'] == 1, 'price_usd']
plt.figure(figsize=(10, 6))
plt.hist(a, bins = 50, alpha=0.5, label='Search Non-Sat Night')
plt.hist(b, bins = 50, alpha=0.5, label='Search Sat Night')
plt.legend(loc='upper right')
plt.xlabel('Price')
plt.ylabel('Count')
plt.show();*
Figure 5
总的来说,非周六晚上搜索,价格更稳定,更低。周六晚上搜索时价格会上涨。似乎这家酒店在周末很受欢迎。
基于聚类的异常检测
k 均值算法
k-means 是一种广泛使用的聚类算法。它创建了“k”个相似的数据点聚类。不属于这些组的数据实例可能会被标记为异常。在开始 k-means 聚类之前,我们使用 elbow 方法来确定最佳的聚类数。
elbow_curve.py
Figure 6
从上面的肘形曲线中,我们看到该图在 10 个聚类之后变平,这意味着增加更多的聚类并不能解释我们的相关变量中更多的变化;在这个案例中price_usd
。
我们设置n_clusters=10
,并在生成 k-means 输出时使用数据来绘制 3D 聚类。
k-means_3D.py
Figure 7
现在我们需要找出要保留的组件(特性)的数量。
PCA.py
Figure 8
我们看到第一个因素解释了将近 50%的差异。第二个成分解释了 30%以上。然而,我们必须注意到,几乎没有一个组件是真正可以忽略的。前两个组件包含 80%以上的信息。所以,我们将设置n_components=2
。
基于聚类的异常检测中强调的假设是,如果我们对数据进行聚类,正常数据将属于聚类,而异常将不属于任何聚类或属于小聚类。我们使用以下步骤来查找和可视化异常。
- 计算每个点与其最近质心之间的距离。最大的距离被认为是异常。
- 我们使用
outliers_fraction
向算法提供关于数据集中异常值比例的信息。不同数据集的情况可能有所不同。然而,作为一个起始数字,我估计了outliers_fraction=0.01
,因为它是在标准化正态分布中,Z 得分距离平均值超过绝对值 3 的观察值的百分比。 - 使用
outliers_fraction
计算number_of_outliers
。 - 将
threshold
设为这些异常值的最小距离。 anomaly1
的异常结果包含上述方法簇(0:正常,1:异常)。- 使用集群视图可视化异常。
- 用时序视图可视化异常。
viz_cluster_view.py
Figure 9
viz_time_series_view.py
Figure 10
似乎 k-均值聚类检测到的异常要么是一些非常高的比率,要么是一些非常低的比率。
隔离森林进行正常检测**
隔离林纯粹基于异常是少量且不同的数据点这一事实来检测异常。异常隔离是在不采用任何距离或密度测量的情况下实现的。这种方法从根本上不同于基于聚类或基于距离的算法。
- 当应用一个 IsolationForest 模型时,我们设置
contamination = outliers_fraction
,也就是告诉模型数据集中异常值的比例是 0.01。 fit
和predict(data)
对数据进行离群点检测,正常返回 1,异常返回-1。- 最后,我们用时间序列视图可视化异常。
IsolationForest.py
Figure 11
基于支持向量机的异常检测
一个 SVM 通常与监督学习相关联,但 OneClassSVM 可用于将异常识别为一个非监督问题,该问题学习异常检测的决策函数:将新数据分类为与训练集相似或不同。
OneClassSVM
根据论文:支持向量机方法进行新颖性检测。支持向量机是最大边际方法,即它们不模拟概率分布。用于异常检测的 SVM 的思想是找到一个函数,该函数对于具有高密度点的区域是正的,而对于低密度点的区域是负的。
- 在拟合 OneClassSVM 模型时,我们设置
nu=outliers_fraction
,它是训练误差分数的上界,也是支持向量分数的下界,必须在 0 到 1 之间。基本上这意味着在我们的数据中我们期望的异常值的比例。 - 指定算法中使用的内核类型:
rbf
。这将使 SVM 能够使用非线性函数将超空间投影到更高维度。 gamma
是 RBF 内核类型的参数,控制单个训练样本的影响——这会影响模型的“平滑度”。通过实验,我没有发现任何显著的差异。predict(data)
对数据进行分类,由于我们的模型是单类模型,所以返回+1 或-1,-1 为异常,1 为正常。
OneClassSVM.py
Figure 12
使用高斯分布的异常检测
高斯分布也叫正态分布。我们将使用高斯分布来开发异常检测算法,也就是说,我们将假设我们的数据是正态分布的。这是一个假设,不能适用于所有的数据集,但当它适用时,它证明了一个发现异常值的有效方法。
Scikit-Learn 的[**covariance.EllipticEnvelope**](https://scikit-learn.org/stable/modules/generated/sklearn.covariance.EllipticEnvelope.html)
是一个函数,它试图通过假设我们的整个数据是一个潜在的多元高斯分布的表达式来计算出我们数据的总体分布的关键参数。过程大概是这样的:
- 基于前面定义的类别创建两个不同的数据集,search_Sat_night,Search_Non_Sat_night。
- 在每个类别应用
EllipticEnvelope
(高斯分布)。 - 我们设置
contamination
参数,它是数据集中异常值的比例。 - 我们使用
decision_function
来计算给定观测值的决策函数。它等于移动后的马氏距离。作为异常值的阈值是 0,这确保了与其他异常值检测算法的兼容性。 predict(X_train)
根据拟合的模型预测 X_train 的标签(1 个正常,-1 个异常)。
EllipticEnvelope.py
Figure 13
有趣的是,通过这种方式发现的异常只观察到了异常高的价格,而没有观察到异常低的价格。
到目前为止,我们已经用四种不同的方法进行了价格异常检测。因为我们的异常检测是无监督学习。在建立模型之后,我们不知道它做得有多好,因为我们没有任何东西可以测试它。因此,在将这些方法应用于关键路径之前,需要对其结果进行实地测试。
Jupyter 笔记本可以在 Github 上找到。享受这周剩下的时光吧!
参考资料:
特定主题的经验:新手专业经验:无行业经验本概述旨在…
www.datascience.com](https://www.datascience.com/blog/python-anomaly-detection) [## sk learn . ensemble . isolation forest-sci kit-learn 0 . 20 . 2 文档
decision_function 的行为可以是“旧的”或“新的”。传递行为= '新’使…
scikit-learn.org](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.IsolationForest.html) [## sk learn . SVM . oneclasssvm-sci kit-learn 0 . 20 . 2 文档
指定要在算法中使用的内核类型。它必须是“线性”、“多边形”、“rbf”、“sigmoid”中的一个…
scikit-learn.org](https://scikit-learn.org/stable/modules/generated/sklearn.svm.OneClassSVM.html) [## sklearn .协方差. elliptic envelope-sci kit-learn 0 . 20 . 2 文档
如果为真,则计算稳健位置和协方差估计的支持,并且重新计算协方差估计…
scikit-learn.org](https://scikit-learn.org/stable/modules/generated/sklearn.covariance.EllipticEnvelope.html) [## 无监督异常检测| Kaggle
编辑描述
www.kaggle.com](https://www.kaggle.com/victorambonati/unsupervised-anomaly-detection)*
Python 中使用 Prophet 进行时间序列预测
在本帖中我们将探讨 facebook 的时间序列模型先知。我们将了解什么是先知和它的优势。我们探索 Prophet 使用一个数据集来了解变化点,如何包括假期,最后使用多个回归变量进行时间序列预测。
什么是先知?
Prophet 是 facebooks 的开源时间序列预测。Prophet 将时间序列分解为趋势性、季节性和假日性。它有直观的超级参数,很容易调整。
先知时间序列=趋势+季节性+假日+误差
- 趋势对时间序列值的非周期性变化进行建模。
- 季节性是周期性变化,如每日、每周或每年的季节性。
- 在一天或一段时间内不定期发生的假日效应。
- 误差项是模型无法解释的。
使用 Prophet 的优势
- 适应多个时期的季节性
- 先知对缺失的价值观有弹性
- 处理 Prophet 中异常值的最佳方法是删除它们
- 模型的拟合是快速的
- 易于调整的直观超级参数
安装 Prophet
使用命令提示符或使用 pip 的 Anaconda 提示符安装 Prophet
**pip install fbprophet**
我们也可以安装 plotly 为 prophet 绘制数据
**pip install plotly**
为 Prophet 创建输入数据
Prophet 的输入是一个至少有两列的数据帧:ds 和 y。
ds 是日期戳列,应该符合 pandas datatime 格式,时间戳为 YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS。
y 是我们要预测或预报的数值列。
Prophet 遵循 sklearn 模型 API 创建 Prophet 的实例,拟合 Prophet 对象上的数据,然后预测未来值。
我们现在直接进入代码,看看变化点,如何包括假期,然后添加多个回归变量。
导入所需的库
from fbprophet import Prophet
from fbprophet.plot import plot_plotly
import numpy as np
import pandas as pdimport matplotlib.pyplot as plt
import plotly.offline as py
py.init_notebook_mode()
%matplotlib inline
从 csv 文件中读取数据
dataset= pd.read_csv(“C:\\avocado-prices\\avocado.csv”)
理解数据
让我们首先来看看列和数据
dataset.head(2)
让我们打印关于数据集的信息,包括列、列的数据类型以及列是否为空
dataset.info()
我们看到两个分类变量,类型和地区。让我们检查一下
dataset.describe(include=’O’)
使用 LabelEncoder 将分类变量转换为数值
我们将具有两个不同值的分类变量 type 转换为数值。为了将分类变量转换成数值,我们使用了 LabelEncoder。在这个例子中,我们没有转换分类变量 region。
**from sklearn.preprocessing import LabelEncoder**le = LabelEncoder()
dataset.iloc[:,10] = le.fit_transform(dataset.iloc[:,10])
dataset.head(2)
type is now converted to numeric
创建输入要素(X)和目标变量(y)
X= dataset[['Date',‘Total Volume’, ‘4046’, ‘4225’, ‘4770’,
‘Small Bags’, ‘Large Bags’, ‘XLarge Bags’, ‘type’]]
y= dataset.iloc[:,1]
为 Prophet 创建数据集
如前所述,Prophet 的输入是一个至少包含两列的数据帧:ds 和 y
train_dataset= pd.DataFrame()
train_dataset['ds'] = pd.to_datetime(X["Date"])
train_dataset['y']=y
train_dataset.head(2)
使用默认值创建和拟合 Prophet 模型
我们将首先探索默认的 Prophet 模型。使用所有默认值创建 Prophet 实例,以适应数据集。
**prophet_basic = Prophet()
prophet_basic.fit(train_dataset)**
预测未来的价值
为了使用 Prophet 预测值,我们需要创建一个带有 ds(datetime stamp)的 dataframe,其中包含我们要进行预测的日期。
我们用***make _ future _ data frame()***给我们指定的天数延伸到未来。默认情况下,它包括历史记录中的日期
**future= prophet_basic.make_future_dataframe(periods=300)
future.tail(2)**
原始数据集中的总行数是 18249,我们看到我们为预测创建的未来数据框包含历史日期和另外 300 个日期。
**forecast=prophet_basic.predict(future)**
绘制预测数据
fig1 =prophet_basic.plot(forecast)
绘制预测组件
我们可以绘制趋势和季节性,预测的组成部分。
fig1 = prophet_basic.plot_components(forecast)
Components of the forecast
给先知增加改变点
变点是时间序列在轨迹中发生突变的日期时间点。
默认情况下,Prophet 向数据集的最初 80%添加 25 个变点。
让我们画出发生潜在变化点的垂直线
**from fbprophet.plot import add_changepoints_to_plot**fig = prophet_basic.plot(forecast)
**a = add_changepoints_to_plot(fig.gca(), prophet_basic, forecast)**
Vertical lines are where changepoints occurred
我们可以查看变化点发生的日期
**prophet_basic.changepoints**
我们可以通过设置 变点 _ 范围 来改变推断的变点范围
**pro_change= Prophet(changepoint_range=0.9)**forecast = pro_change.fit(train_dataset).predict(future)
fig= pro_change.plot(forecast);
a = add_changepoints_to_plot(fig.gca(), pro_change, forecast)
在初始化 prophet 时,可以使用n _ change points参数设置变点数
**pro_change= Prophet(n_changepoints=20, yearly_seasonality=True)**forecast = pro_change.fit(train_dataset).predict(future)
fig= pro_change.plot(forecast);
a = add_changepoints_to_plot(fig.gca(), pro_change, forecast)
调整趋势
Prophet 允许您调整趋势,以防过度拟合或拟合不足。change point _ prior _ scale帮助调整趋势的强弱。
change point _ prior _ scale 的默认值为 0.05。减小该值会降低趋势的灵活性。增加change point _ prior _ scale的值,使趋势更加灵活。
将change point _ prior _ scale增加到 0.08,使趋势更加灵活
**pro_change= Prophet(n_changepoints=20, yearly_seasonality=True, changepoint_prior_scale=0.08)**
forecast = pro_change.fit(train_dataset).predict(future)
fig= pro_change.plot(forecast);
a = add_changepoints_to_plot(fig.gca(), pro_change, forecast)
将change point _ prior _ scale减小至 0.001,以降低趋势的灵活性
**pro_change= Prophet(n_changepoints=20, yearly_seasonality=True, changepoint_prior_scale=0.001**)
forecast = pro_change.fit(train_dataset).predict(future)
fig= pro_change.plot(forecast);
a = add_changepoints_to_plot(fig.gca(), pro_change, forecast)
添加假日
节假日和事件会导致时间序列发生变化。在我们的例子中,7 月 31 日的全国鳄梨日和 9 月 16 日的鳄梨日会影响鳄梨的价格。
我们可以通过创建一个包含两列 ds 和 holiday 的 dataframe 来为 Prophet 创建一个定制的假日列表。假日的每一个事件占一行
**avocado_season = pd.DataFrame({
'holiday': 'avocado season',
'ds': pd.to_datetime(['2014-07-31', '2014-09-16',
'2015-07-31', '2015-09-16',
'2016-07-31', '2016-09-16',
'2017-07-31', '2017-09-16',
'2018-07-31', '2018-09-16',
'2019-07-31', '2019-09-16']),
'lower_window': -1,
'upper_window': 0,
})**
下部窗口和上部窗口将假期延长至日期前后的天。如果我们想包含国家鳄梨日和鳄梨酱日之前的一天,我们设置lower _ window:-1 upper _ window:0
如果我们想使用假期后的一天,那么设置lower _ window:0 upper _ window:1
pro_holiday= Prophet(**holidays=avocado_season**)
pro_holiday.fit(train_dataset)
future_data = pro_holiday.make_future_dataframe(periods=12, freq = 'm')
#forecast the data for future dataforecast_data = pro_holiday.predict(future_data)
pro_holiday.plot(forecast_data);
添加多个回归变量
可以向 Prophet 模型中添加额外的回归变量。这是通过使用 add_regressor 来完成的。拟合和预测数据框架中都需要有额外的回归变量 列值。
使用附加回归量创建拟合和预测数据集
train_dataset[‘type’] = X[‘type’]
train_dataset[‘Total Volume’] = X[‘Total Volume’]
train_dataset[‘4046’] = X[‘4046’]
train_dataset[‘4225’] = X[‘4225’]
train_dataset[‘4770’] = X[‘4770’]
train_dataset[‘Small Bags’] = X[‘Small Bags’]train_X= train_dataset[:18000]
test_X= train_dataset[18000:]
我们对数据集进行了分割,以展示额外回归变量的使用,因为我们需要拟合和预测数据框中所有额外回归变量的值
#Additional Regressor
**pro_regressor= Prophet()
pro_regressor.add_regressor('type')
pro_regressor.add_regressor('Total Volume')
pro_regressor.add_regressor('4046')
pro_regressor.add_regressor('4225')
pro_regressor.add_regressor('4770')
pro_regressor.add_regressor('Small Bags')**#Fitting the data
**pro_regressor.fit(train_X)
future_data = pro_regressor.make_future_dataframe(periods=249)**#forecast the data for Test data
**forecast_data = pro_regressor.predict(test_X)**
pro_regressor.plot(forecast_data);
Prediction using additional regressor
预测数据是最后的蓝色阴影区域。
Jupyter 笔记本可用此处
参考资料:
https://facebook.github.io/prophet/docs/quick_start.html
https://peerj.com/preprints/3190.pdf
人工智能专家:是时候让守护者保护我们自己了
计算机越来越多地使用我们的数据来对我们做出决定,但我们能信任它们吗?
每天,在你不知情的情况下,计算机算法会利用你的数据来预测你的习惯、偏好和行为。
他们认为你喜欢 YouTube 上的猫视频意味着你会收到胡须广告的垃圾邮件,或者你的披头士下载意味着你想听保罗·麦卡特尼的第 100 首单曲。
如果你喜欢被推荐的音乐,或者不觉得广告反映了你令人毛骨悚然的网络浏览,那么你可能不会介意。
但是通过算法做出决策要更进一步。
算法正在决定谁能通过护照检查,谁能收到债务催收通知、房屋贷款和保险,甚至谁会成为警察的目标以及他们的刑期有多长。
最近,透露了算法告诉经期跟踪应用程序通知脸书你可能怀孕了。
算法基本上是计算机如何处理它接收到的数据的一组指令。
随着越来越多的系统变得自动化和个性化,这些输入越来越成为我们的个人数据:手机位置、社交媒体或应用程序使用习惯、网页浏览历史,甚至健康信息。
问题在于,你永远不知道一种算法是如何处理你的数据并做出决定的:你的抵押贷款申请被拒绝是基于你的未付账单历史还是你头发的颜色。
事实上,你对决策过程没有任何意见,你可以保证你的利益将永远支持那些开发应用程序的人。
Your trail of mobile and other data is being monetised daily
一群领先的计算机科学家最近一直在讨论在这个新兴系统中更好地保护我们自己的必要性。
他们说如果不采取行动,我们将失去对个人数据的控制,也失去对我们决策的透明度。
RMIT 大学副教授 Flora Salim、UNSW 大学教授 Salil Kanhere 和迪肯大学教授 Seng Loke 提出的解决方案之一是编写我们自己的算法监护人。
什么是“算法守护者”?
算法守护者将会是个人助理机器人,甚至是伴随我们到任何地方的全息图,并提醒我们在线幕后发生的事情。
这些守护者本身就是算法,但它们只为我们工作,被编程为根据我们的个人偏好管理我们与社交平台和应用程序的数字交互。
它们可以根据我们的意愿改变我们的数字身份,对不同的服务应用不同的设置,甚至在我们选择的时候让我们变得可识别或匿名。
我们的监护人可以确保我们的备份和密码是安全的,并允许我们决定在我们的在线存在中记住什么和忘记什么。
实际上,算法监护人将:
- 如果我们的位置、在线活动或对话被监控或跟踪,提醒我们,并给我们选择消失
- 当我们注册在线服务时,请帮助我们理解冗长繁琐的条款和条件的相关要点
- 当我们不明白我们的电脑、电话记录和手机后台运行的数十个应用程序之间的数据发生了什么变化时,请给我们一个简单的解释
- 如果有应用程序将我们手机中的数据发送给第三方,请通知我们,并让我们选择实时阻止它
- 请告诉我们,我们的数据是否被第三方货币化,以及用途是什么。
算法守护者被设想为当前个人助理(如 Siri、Alexa 或 Watson)的下一代。
他们不需要像人类一样聪明,只要与他们居住的环境相关就行——识别其他算法并解释他们在做什么。
如果没有这种责任感,我们生活中的关键时刻将越来越多地被未知的、看不见的和任意的算法所影响。
算法监护人将承担沟通和解释这些决定的重要角色。
可解释的机器学习是人工智能研究中越来越感兴趣和活跃的一个领域,它试图提供对算法如何做出最终决定的洞察。
既然算法已经渗透到日常生活中,可解释性不再是一种选择,而是一个迫切需要进一步关注的领域。
算法守护者何时到来?
就在我们说话的时候,实现算法守护者的技术正在出现,滞后的是我们需要它们的普遍意识。
你可以在存储和管理密码的数字保险库中,以及在让我们对如何使用我们的数据进行一些控制的软件设置中,看到算法守护者技术的原始版本。但是在普适计算时代,需要更全面的东西。
该团队表示,我们需要在未来几年内开发特定的算法监护人模型,为未来十年的开放算法系统奠定基础。
需求肯定是存在的,是吗?
在过去的十年里,隐私的概念发生了根本性的变化。
有没有可能在下一个十年里,我们甚至不会在乎每个系统都知道我们的一切,并利用这些信息为所欲为,因为大多数情况下,它都工作得很好?
本文改编自《对话》中出现的 一片 ,作者为萨利姆、坎 here 和洛克。
迈克尔·奎恩
是时候停止在虚假点击上浪费金钱了!
用机器学习处理点击欺诈
Image source: https://www.bankinfosecurity.com/click-fraud-kingpin-receives-7-year-sentence-a-9072
什么是点击欺诈?
隐藏在 300×250 像素横幅后面的是一个复杂的数字广告生态系统。与传统广告业相比,互联网时代的新商业模式涉及广告专员(也称为广告网络),充当广告商和内容出版商之间的经纪人。
广告商计划预算,向专员提供广告,并就每个客户行为(例如,点击广告、填写表格、在拍卖中出价等)商定佣金。内容发行商与专员签订合同,在他们的网站上显示广告,并根据它给广告商带来的流量获得佣金。然而,这种模式可能会刺激不诚实的出版商在他们的网站上产生非法点击——这是一个被称为点击欺诈的主要问题。
数据介绍
这篇文章描述了我的个人机器学习项目,即针对移动应用广告的点击欺诈预测。所有的源代码都可以在我的 Github 上找到:https://github.com/jystacy/Click_Fraud。这个项目来源于 Kaggle 的 TalkingData AdTracking 欺诈检测挑战赛,旨在预测用户点击移动应用广告后是否会下载某个应用。这个人工数据集的整个时间范围包括 27 分钟。
数据集的每一行都包含一条点击记录,具有以下特征:
ip
:点击的 ip 地址。app
:营销用 app id。device
:用户手机的设备类型 id(如 iphone 6 plus、iphone 7、华为 mate 7 等。)os
:用户手机的 os 版本 idchannel
:移动广告发布商的渠道 idclick_time
:点击时间戳(UTC)is_attributed
:要预测的目标,表示已经下载了 app
Summary of five original categorical features
检查标记特征的分布总是很重要的。这是一个非常不平衡的数据集,99.65%的点击都没有导致应用程序下载。为了避免即使随机猜测也能达到 99%以上的准确率,我将在数据分割后处理不平衡的挑战。
数据准备
高基数属性
数据是干净的,没有应用任何技术,因为具有七个特征的所有数据记录都是完整的。但是,值得注意的是,具有超过 100 个不同值的分类特性(应用程序、设备、操作系统和渠道)可能具有很强的预测性,同时会对包含在预测模型中造成问题。
包含这种高基数属性的一种优雅而简单的方法是将名义变量转换为一个连续变量,其值与目标标签相关。我选择了监督比率来转换这些分类特征:用一个类别中正面实例的百分比(is_attributed = 1)替换名义上的表示值。
特征工程
给定原始变量可以提供的有限信息,特征生成将对模型性能产生强烈影响。以点击计数为导向的收入分享系统为虚假点击提供了肥沃的土壤,所以我试图创建一系列特征来捕捉点击行为的异常行为模式。某段时间内来自某个 ip、某个设备、某个操作系统、某个应用程序或某个渠道的点击次数都可能是很好的预测指标。我利用(然后删除)前 60 秒内的点击记录来为所有样本生成额外的预测值。
我创建了 25 (=5*5)功能,通过计算特定 ip、操作系统或设备或通过特定应用程序或渠道产生的点击记录之前 1、3、10、30 和 60 秒内的点击计数。对于这五个原始分类特征(ip、os、app、频道和设备)的每个组合,40(= 5 (5–1) 2)更多创建的交互时间窗口计数特征集中在每个点击记录之前的 10 秒和 3 秒的时间间隔。例如, ip_channel_10s 表示在过去 10 秒内对特定频道做出点击的唯一 ip 的计数,而 channel_ip_10s 表示特定 ip 在过去 10 秒内点击的频道的计数。
通过使用 python 中的多处理库并行计算对超过 1 亿个值的特征工程实现过程进行了超过 10000%的优化。我的 Github 上也有代码。
误差分配
我执行了 80/20 的训练-测试分割,使用前 80%的点击进行训练,后 20%作为测试数据,因为没有点击和欺诈的时间序列模式(如 EDA 部分的前两个图表所示)。然后,我对大多数进行随机下采样,以组成一个平衡的训练数据集。在平衡数据之后,我再次输出类分布来可视化差异。
数据缩放
在这个项目中,数字特征的范围变化很大。如果数据集中的某个要素与其他要素相比规模较大,则此大规模要素将成为主导要素,需要针对测量欧氏距离的算法(如 KNN 和 SVC)进行归一化。我使用 scikit-learn 库中的 StandardScaler 进行数据缩放。
探索性数据分析
我是从点击记录和欺诈点击的时间序列分布开始 EDA 的。然而,在这种情况下,数据收集的持续时间太短,无法显示时间序列模式。因此,我没有从点击时间特性中提取小时或分钟属性。相反,我根据时间序列分割数据集,而不考虑偏差。
在生成了数百万个基于点击计数的数值变量之后,需要检查属性之间的相关性。从相关性热图来看,这些累计计数属性之间存在高度的正相关性,比如一个 app 或一个频道在过去 1 秒和 3 秒内获得的点击计数。因此,在线性模型的模型训练期间,主成分分析(PCA)将有助于更好的模型性能。
作为可视化探索的最后一步,我们来检查一下数据的可分性。为了可视化高维数据集,我选择了 t 分布随机邻居嵌入 ( t-SNE ),这是另一种比 PCA 更适合可视化的降维技术,旨在" 最小化两种分布之间的差异:一种分布测量输入对象的成对相似性,另一种分布测量嵌入中相应低维点的成对相似性。
具有两个结果维度的平衡训练数据的散点图可以在模型选择方面提供提示。例如,图表显示,在这种情况下,SVM 极有可能表现不佳。
模特培训
性能赋值
如何评估模型的性能应该与项目的目标相匹配。这种情况下的目标是从不平衡的数据集中检测虚假点击,并预测用户在点击移动应用广告后是否会下载应用。因此,我使用了 ROC-AUC (受试者操作特征-曲线下面积)性能指标。ROC 是概率曲线,AUC(0 和 1 之间的值)代表可分性的程度或度量。它告诉我们模型在多大程度上能够区分不同的类。如果一个算法的 ROC-AUC 得分高于 0.5,则它比随机猜测获得了更高的性能。
型号选择
本项目中考虑的模型包括最流行的分类算法:
- 逻辑回归
- 线性判别分析
- k 最近邻(KNN)
- 分类树
- 支持向量分类器
- XGBoost 分类器
- 随机森林分类器
模型选择的输入数据是所有的数字计数特征。我使用 10 重交叉验证来评估模型在训练数据集上的性能。我为不同类型的分类模型定制了输入数据。我将 PCA 生成的 13 个特征(保留了 95%的方差)输入线性模型——逻辑回归和 LDA。对于基于距离的算法(KNN 和 SVC),所有输入数据都是标准化的。
由于在点击欺诈的情况下强调的是预测欺诈点击的能力,而不是可解释性,XGBoost 在 ROC-AUC 得分方面无疑优于其他模型。此外,SVM 的 ROC-AUC 评分验证了 t-SNE 可视化产生的猜测。
XGBoost 的模型训练
XGBoost 本质上不受特征量和多重共线性的影响。我的 XGBoost 性能改进策略包括向模型中添加转换后的高基数分类变量以增加分类特征提供的信息量,使用验证数据集仔细调整超参数以在过度拟合和欠拟合之间进行权衡,以及根据特征重要性分析优化输入特征的数量。
- 没有分类特征的优化模型:
- 具有高基数分类特性的优化模型:
- 具有转换的高基数分类特征的优化模型:
结论
我最终选择了经过良好调优的 XGBoost 模型,它有 20 个最重要的属性,包括数字点击计数变量和转换后的高基数分类特性。测试数据集上的 ROC-AUC 得分达到 0.969132,同时避免了过拟合。
在记录点击的频道方面的特征在特征重要性方面排名最高的发现表明不诚实的移动广告发布者倾向于集中而不是分散某些频道上的欺诈点击,以便为高点击计数要求更高的奖励。
下一步
类别特征的实体嵌入
说到分类特征,有几种处理特征包含的技术。由于高基数特征一键编码的计算效率低和潜在的稀疏性问题,一种源于神经网络的新技术——实体嵌入受到越来越多的关注。
实体嵌入与我在项目中使用的转换方法(监督比率)有相似的原则,即产生的嵌入向量是类别的表示,其中相似的类别(相对于任务)彼此更接近,而实体嵌入在嵌入向量的维度上更灵活。后一个特征提高了揭示范畴变量内在连续性的能力,同时可以保留不同基数之间的更多差异。所以我计划将实体嵌入应用于分类特征的转换,作为下一步的改进。
请随时查看我的 Github 上的所有代码以供参考:https://github.com/jystacy/Click_Fraud。
热烈欢迎所有的反馈和讨论。😊
Time2Vec 用于时间序列特征编码
为您的机器学习模型学习一个有价值的时间表示
Photo by Luca Micheli on Unsplash
在每个涉及时间序列的机器学习问题中,时间是黄金信息。作为数据科学家,我们必须尽最大努力提取时间模式,让我们的数据自己说话。在预处理过程中,常见的程序有标准化(平稳性检查、自相关消除…)、创建和编码分类时间特征(日、周、月、季…)、人工特征工程(傅立叶变换…)。我们的努力并不总是有回报的,因为在模型化过程中,我们选择的模型可能无法正确地将时间本身视为一个特征。
在这篇文章中,我试图再现论文’ Time2Vec:学习时间的向量表示法 ’ 【T5,中提出的方法,其最终目标是开发一种通用的模型不可知的时间表示法,这种表示法可以潜在地用在任何架构中(我在 Keras 中开发了一个神经网络,采用了这种解决方案)。作者不想为时间序列分析提出一个新的模型,相反,他们的目标是以向量嵌入的形式提供时间的表示,以便以更好的方式自动化特征工程过程和建模时间。
数据集
为了给出证据和整个解决方案的具体效用,我们需要一个足够的数据集。在我的例子中,我需要时间序列格式的数据,没有任何额外特征形式的冗余信息。这种情况在自回归问题中很典型,在这种情况下,我们只有一个时间序列,并将其作为预测未来的一个特征。在现实生活中这是一个常见的任务,所以不难找到一个数据集。我在 Kaggle 上发现了一个不错的。它储存了威尼斯大量的历史水位。预测这些值是一项严肃的任务;每天游客都可以获得该城市不同地区海平面的详细而准确的报告。
我爱威尼斯,我的目标不是和他们竞争。我们也没有足够的信息来提供真正明显的表现(额外的回归因素,如温度、月相、天气条件等,提供了足够的推动)。在这里,我们不得不利用仅有的历史数据来预测下一个小时的水位。
Example of hourly water level (cm) in a week
时间 2 秒实现
从数学角度来说,实现 Time2Vec 非常容易:
from: https://arxiv.org/pdf/1907.05321.pdf
其中 k 是时间 2 维,τ是原始时间序列, F 是周期性激活函数,ω和φ是一组可学习的参数。在我的实验中,我将 F 设置为一个 sin 函数,以使选定的算法能够捕捉数据中的周期性行为。同时,线性项代表时间的进程,可用于捕捉依赖于时间的输入中的非周期性模式。
这种简单性使得这种时间向量表示很容易被不同的架构所使用。在我的例子中,我试图在修改简单 Keras 密集层的神经网络结构中转移这个概念。
class T2V(Layer):
def __init__(self, output_dim=None, **kwargs):
self.output_dim = output_dim
super(T2V, self).__init__(**kwargs)
def build(self, input_shape): self.W = self.add_weight(name='W',
shape=(input_shape[-1], self.output_dim),
initializer='uniform',
trainable=True) self.P = self.add_weight(name='P',
shape=(input_shape[1], self.output_dim),
initializer='uniform',
trainable=True) self.w = self.add_weight(name='w',
shape=(input_shape[1], 1),
initializer='uniform',
trainable=True) self.p = self.add_weight(name='p',
shape=(input_shape[1], 1),
initializer='uniform',
trainable=True) super(T2V, self).build(input_shape)
def call(self, x):
original = self.w * x + self.p
sin_trans = K.sin(K.dot(x, self.W) + self.P)
return K.concatenate([sin_trans, original], -1)
该自定义层的输出维度是用户指定的隐藏维度 (1 ≤ i ≤ k) ,即从网络学习的正弦曲线,加上输入的线性表示 (i = 0)。有了这个工具,我们只需将它与其他层堆叠在一起,并在我们的案例研究中尝试它的威力。
模型
Time2Vec 是一个很好的时间表示吗?为了回答这个问题,我比较了在我们的预测任务中实现的性能,建立了两个不同的序列神经网络模型。第一个将我们定制的 Time2Vec 层作为输入,叠加在一个简单的 LSTM 层上。第二层仅由先前结构中使用的简单 LSTM 层组成。
def T2V_NN(param, dim):
inp = Input(shape=(dim,1))
x = T2V(param['t2v_dim'], dim)(inp)
x = LSTM(param['unit'], activation=param['act'])(x)
x = Dense(1)(x)
m = Model(inp, x)
m.compile(loss='mse', optimizer=Adam(lr=param['lr']))
return mdef NN(param, dim):
inp = Input(shape=(dim,1))
x = LSTM(param['unit'], activation=param['act'])(inp)
x = Dense(1)(x)
m = Model(inp, x)
m.compile(loss='mse', optimizer=Adam(lr=param['lr']))
return m
我们执行拟合程序操作超参数优化。这是使用keras-hype tune完成的。该框架以非常直观的方式提供了神经网络结构的超参数优化。我们对一些参数组合进行标准网格搜索。所有涉及的两个训练程序都要这样做。
结果
我使用前 70%的数据作为训练集,剩下的 30%作为测试集。在拟合过程中,该系列还分为验证,以便进行超参数调整。最佳和优化的 T2V + LSTM 在测试中实现了大约 1.67 MAE,而简单和优化的 LSTM 实现了大约 2.02。
这两个网络似乎都能很好地识别数据中的模式。使用 T2V 编码可以稍微提高性能。
摘要
在这篇文章中,我介绍了一种自动学习时间特征的方法。特别是,我复制了 Time2Vec ,一个时间的向量表示,使其适应神经网络架构。最后,我能够在一个真实的任务中展示这种表现的有效性。我想指出的是,正如论文作者所建议的,T2V 不是时间序列分析的新模型,而是一种简单的向量表示,可以很容易地导入到许多现有和未来的架构中,并提高它们的性能。
如果你对题目感兴趣,我建议:
保持联系: Linkedin
参考文献
Time2Vec:学习时间的向量表示法*。赛义德·迈赫兰·卡泽米、里沙卜·戈埃尔、塞佩赫尔·埃格巴利、贾纳汉·拉曼南、贾斯普里特·萨霍塔、桑杰·塔库尔、斯特拉·吴、卡塔尔·史密斯、帕斯卡尔·普帕特、马库斯·布鲁贝克*
时代在变,成员在变动
预测成员退出健康计划的最佳实践
Photo by rawpixel.com
每年健康计划和提供者花费大量的时间、资源和金钱来留住他们的成员。实际上,有专门关注会员参与度和忠诚度的会议,允许组织从行业领导者那里学习技巧和诀窍。
留住会员的一个策略是问“谁可能退出我的计划”这个问题。预测会员退保(“流失”)在任何计划选择由个人决定的会员群体中都是一个极其常见的用例,我将在下面解释原因。
让我来布置舞台。
每年有 11%的 Medicare Advantage 计划参与者自愿转投另一项计划(凯撒家庭基金会)。为了取整数,我们就把它定为 10%吧。为了说明 10%的退保率对计划收入的实际意义,假设您有 50,000 名成员,平均每个成员每月支付 700 美元(每个成员每年 8400 美元)的保费。如果你的组织分享了全国平均 10%的会员流失率,这意味着 5000 名会员将选择退出你的计划——损失你 4200 万美元的补偿。是的,你没看错——4200 万美元!
年复一年保持 100%的会员是不现实的,但是如果你能把退保率从 10%降低到 9%会怎么样?1%的人口对你的计划收入有多大影响?
当你减少 1%的会员流失率,你就能保留每年 420 万美元的收入!
Photo by Ben White
1%有如此大的影响,可以理解的是,健康计划和提供者正在寻找解决方案,以确定哪些成员最有可能退出他们的计划。被认定为“高风险”的成员可以加入激励/干预计划,以期改变预测的结果。
如果您希望在内部构建这一模型,或者希望使用预测分析解决方案,我们已经找到了一些最佳实践,希望您在尝试预测会员流失时谨记在心。
1.快速创建基线模型
在他在 Coursera 的机器学习课程中,吴恩达解释说,当建立一个新的机器学习模型时,你应该尽可能快地得到第一个结果,最好是在 24 小时内。我们同意!
这一切都是为了展示潜在的投资回报率。获取您可用的数据源,并获得将作为基线预测模型的第一个结果。我们经常看到团队一开始就试图构建最复杂的模型。但是如果你的数据中没有足够的信号来预测你想要的结果呢?从一个轻量级模型开始,看看最初的预测是什么样的。仅使用现成的数据流快速获得基线模型,有助于证明是否有足够的 ROI,并确定为提高模型准确性而付出的额外努力的价值。
具体到会员流失,从一年的索赔和录取数据开始。仅这些数据源就应该产生一个简单而有洞察力的基线模型。它可能没有你理想中想要的那么准确,但是它将能够识别风险极高的成员,并证明是否应该花更多的时间来更深入地开发模型。
当与您的主管、人口健康副总裁、首席财务官或首席执行官交谈时,交流投资回报看起来有所不同。为了帮助向您的组织传达您的预测模型的明确价值,ClosedLoop 在平台中内置了 ROI 图。有许多不同的观点可以根据你的受众来定制信息。
Image from ClosedLoop.ai Platform
在闭环,我们用一些主要参数计算 ROI。人口规模、结果成本(您的组织将花费多少干预成本)、干预成本(您的组织运行特定干预的成本)和干预功效(干预的有效性)。
2.使用所有可用的患者数据
机器学习从多个数据源中提取有用的模式,以建立更准确和全面的风险评分。一旦建立了基线模型,就开始测试额外的患者可链接数据(ADT feeds、EMR、社会因素、实验室等)的预测价值。).这里的一行外卖— 使用所有可用的患者数据 。
如果你计划在内部建立这个模型,这个过程就变得非常昂贵和劳动密集。首先,想想你的数据来自多少不同的内部部门和外部组织,然后想想每个来源中的数据是如何以自己特定的方式格式化的。以索赔中的患者 ID 列为例。它将位于数据集中数百列中的一列。现在,看看您的 ADT 提要或 EHR,不仅这个患者 ID 不在同一列,而且它可能有完全不同的列标题。如果这还不够混乱的话,还有一个病人 ID 栏,但它与您声明中的 ID 不同。手动搜索多个数据源中的数百列既耗时又令人困惑,而且没有数据科学家愿意这样做!
ClosedLoop 在平台中内置了许多不同的功能,可以自动接收不同的数据源,包括定制的专有数据集。在这种自动化方法中,测试和试验哪些数据源会产生增量预测值只需最少的工作。因为工作量很小,所以尽可能多地测试数据源!
在预测会员流失时,我们发现了几个可以提高模型准确性的外部数据源。例如社会决定因素数据,如美国农业部食物图谱和人口普查数据(也是公开的!)可以提供大量的价值,让你了解你的人口的营养和经济状况。现在,从这些数据源本身开始不会产生很好的结果(这是非常棘手的),但是将它们放在一个基线模型之上是非常有效的,该模型包含带有患者人口统计数据的声明。为你的预测模型提供支持的数据源越多,你的风险评分和预测就越全面,越有解释力。
3.采取行动
太好了,你已经确定了有风险的成员。但是,预测谁最有可能退出你的计划只是拼图的第一部分。你打算如何处理这些预测?
对处于危险中的成员进行干预,是您的组织采取行动并试图改变预测结果的机会。但是你的干预应该是什么呢?它可能会打电话给高风险会员,询问他们是否喜欢他们的医生,如果不喜欢,就提议安排下一次与新医生的预约。也许这是一个真正的来自护理经理的电话提醒,而不是来自自动线路。对于退出网络的会员,干预措施可以是建议转介给网络内提供者。
我们建议以多种方式利用分析。同样,如果你计划在内部建立这个模型,这一块是相当棘手的。通过为相似的患者群体提供个性化的护理方法,提供多种干预措施是非常有益的,但是您如何决定哪个患者从哪种干预措施中受益最大呢?
当针对特定的患者群组采取不同的干预措施时,了解某个成员被确定为高风险的原因会有所帮助。ClosedLoop 正是提供了对预测的人类可读的解释,称为起作用的因素。这些因素使您可以看到某人被确定为高风险的确切原因,从而更容易将特定的成员群组与不同的干预措施相关联。
预测会员流失是所有健康计划的首要任务,会员选择是个人的决定,在看到 1%的会员可能产生的影响后,很容易明白为什么会这样。如果您希望构建这种预测模型或与解决方案提供商合作,我们希望您记住这些最佳实践。
**快速建立模型:**尽可能快地建立一个新模型,然后进行迭代以提高模型的准确性。
**使用所有可用的患者数据:**添加标准和专有数据集,为每个成员创建更准确和全面的风险评分。
**采取行动:**没有行动的预测是对时间、金钱和资源的巨大浪费。让会员参与不同的干预措施,以期主动改变预测的结果。
作者:Allyson CIA burri| closed loop . ai |医疗保健营销
原载于 2019 年 6 月 13 日https://closed loop . ai。
时间序列数据管理—分布在多个组中的滞后变量
Photo by Jon Tyson on Unsplash
方法链接、分组和索引操作
对时间序列数据建模可能具有挑战性,因此一些数据爱好者(包括我自己)推迟学习这个主题直到他们绝对必须这样做是有道理的。在将机器学习模型应用于时间序列数据之前,您必须将其转换为模型的“可吸收”格式,这通常涉及计算滞后变量,这可以测量自相关,即变量的过去值如何影响其未来值,从而释放预测值。以下是我最近用来生成熊猫滞后变量的三种不同方法:
1。在一组中滞后一个或多个变量—使用 移位 方法
2。跨多个组延迟一个变量—使用 分解 方法
3。跨多个组延迟多个变量—使用 groupby
首先,让我们生成一些虚拟时间序列数据,因为它将“在野外”出现,并将它放入三个数据帧中用于说明目的(所有代码在一个地方)。
其次,做一些转换,让 pandas 将您的 dates 列识别为一个 datetime 变量。这对我们以后会有帮助。旁注,显然是熊猫的策划者,韦斯·麦金尼,特别发明了熊猫来解决处理时间索引数据的问题。因此,在生成虚拟数据并转换日期列之后,您现在应该有 3 个数据帧,如下所示。
现在是有趣的部分!第一种方法非常简单,如下所示。
1。在一个组/类别中滞后一个或多个变量——使用“移位”方法
这里,我们简单地使用可用于 dataframe 的 shift 方法,并指定在将日期列设置为索引后要延迟的步数(在我们的例子中,是 1“天”)。记住,你也可以使用负数作为位移,这意味着未来的值正在影响过去(时间机器,有人知道吗?😊).
df_onegrp.set_index(["date"]).shift(1)
注意第一行现在有空值,表明我们确实滞后了数据。如果您落后两步,前两行将为空等。对于这个例子,索引已经是惟一的了,但是您可能希望对其他数据进行检查,以避免意外的后果。
2。将一个变量延迟到多个组——使用“拆分”方法
这种方法稍微复杂一些,因为有几个组,但易于管理,因为只有一个变量需要滞后。总的来说,我们应该意识到,我们希望首先对数据进行索引,然后在应用 lag 函数之前,通过 T2 分解来分离组。不这样做实际上会对您的数据做错误的事情。例如,它可以将前一个组的最后一个值移动到下一个组的第一个值上,从而跨组混合数据。想象一下,如果这些群体是国家,变量是以年为单位的人口。如果你犯了这样的错误,这就像假设宾夕法尼亚州晚年的人口会影响罗德岛早年的人口,因为这两个州按字母顺序相邻。这说不通。这可能是一个巨大的陷阱,会导致完全错误的分析。
因此,将索引设置为您的日期和组。然后使用拆垛拉出组,然后移动列,就像前面的方法一样。更多关于堆叠、拆解技巧的信息,请看我的另一篇 文章这里 。
现在将组放回行中。注意如何保存空值,然后对它们做任何你想做的事情(替换,删除等等)。)
为了便于说明,我选择保留空值/缺失值并重新排列数据
df.reset_index().sort_values("group")
好的,如果你密切关注,你会注意到这种方法也适用于许多组中的许多变量,但是我想把这个场景留给另一种技术,在下一节中使用 groupby 来代替。所以,你可以停止阅读,使用上面的方法。但是你很好奇,所以我们继续…
Photo by Joakim Honkasalo on Unsplash
3。 同时延迟分布在多个组中的多个变量——使用“分组依据”方法
这个方法依赖于 pandas groupby 函数结合我们在前面的方法中学到的所有招数。所以我不会花时间解释我们已经讨论过的概念。关键步骤是使用“组”列对数据进行分组;制作一个函数,使对进行排序,按日期对进行索引,对每组中的数据进行移位,然后通过对象对组中的进行迭代,将函数应用于每组。最后,我们使用便利的列表理解,然后将这个列表连接回其原始格式。
这种方法中使用的一些很酷的概念是:
- 赋值 : 我最近发现了数据帧可用的赋值方法。它清理了您的代码,允许您生成一个新列并为其分配新值,而不必使用中间的 dataframe 。它会自动返回数据的副本。
- 方法链接 : 该函数还在返回语句中使用括号,以便允许方法链接,这是编写干净、易读代码的另一种方式。
将数据分组:
grouped_df = df_combo.groupby(["group"])
使用赋值和方法-链接创建功能:
使用列表理解对每个组应用函数,然后垂直连接:
dflist = [lag_by_group(g, grouped_df.get_group(g)) for g in grouped_df.groups.keys()]pd.concat(dflist, axis=0).reset_index())
这就对了。滞后时间序列的三种不同方法。可能还有更多,例如,您可以通过 x 步骤移动数据,然后删除每个组的第一个 x 步骤,但是我认为上述解决方案更加健壮和灵活,允许您对空值做任何您想做的事情。下面让我知道你的想法。
编码快乐!
修补张量和其他伟大的冒险
Source: www.pexels.com
在(不严格地)保持理智的同时,思考如何实现你的第一篇深度学习论文。
在 2018 年底一个普遍不起眼的周三,我听从了互联网明智的建议,踏上了通往人工智能中心的痛苦旅程;我开始在自然语言处理(NLP)中实现深度学习研究论文。
经过许多天驯服怪物、追捕真正科学家的材料、凝视黑暗之心,我出现了疲惫,但胜利。接下来是对解决这样一个项目的动机和期望的描述。
如果你也敢冒险进入深海,那就加入我吧。
…正如 Ilya 喜欢说的那样,你需要做好遭受痛苦的准备:预计将花费数小时调试拒绝学习的模型,许多遍重新构造你的代码,并为改变各种超参数建立你自己的约定。但是每次你遭受痛苦的时候,要知道你已经积累了一点技能,这对未来是无价的。
——格雷格·布罗克曼, Quora 05/16
动机
为什么要实现一篇研究论文?为什么是 NLP?
先说后者。
假设你想为人类的持续利益创造有益的人工智能(我的意思是,谁不想呢?),你就必须创造一个具有高级推理能力的系统,并且能够更好地向人类解释其意识的内容。自然语言理解(NLU)是处理这些基本问题的人工智能的一个子领域,它本身嵌套在更大的自然语言处理学科中。
更简单地说, 教机器理解语言对于安全解开智能本身的奥秘至关重要。
此外,如果你关注了 2018 年发布的flurry论文 ,你就会知道,尽管计算机视觉凭借其在深度学习方面的突破和应用长期以来一直吸引着公众的注意力,NLP 的时代已经到来。这一点,以及我在硅谷一家对话式人工智能初创公司担任机器学习工程师的角色,极大地激发了我对这一领域的兴趣。
所以,这里有两个令人信服的理由来尝试和实现一篇研究论文:
- 增进你的理解。不弄脏自己的手,你根本无法获得理论在实践中如何运作的良好直觉,而完成一篇论文会弄脏自己的手。 如果你一直在利用 书籍、在线 课程、和 录制的 讲座自学深度学习的课题,这是一个必不可少的下一步,它会无情地暴露你知识中的任何缺口。
- 成为一个坏蛋。复制一篇论文的表现并不简单,这将考验你工程能力的极限以及你的毅力。无论你已经做了一段时间,还是你刚刚开始做这个领域,最终完成这样一个项目可以作为你的“成年”仪式。 它将标志着你从一个被动的观察者转变为人工智能研究社区的积极参与者。你还会积累谦逊和自信,这样你就可以在未来面对更具挑战性的项目。
如果这些听起来有一点点有趣,那么你来对地方了。卷起袖子,涂上防晒霜,我们上路吧。
Let’s hit the road! Source: www.pexels.com
但是首先,像任何这种性质的冒险一样,我们要确保我们意识到前面的任何危险…
预期
你复制一篇研究论文的经历将受到你实现个人目标的成功程度的影响。因此,当你开始你的旅程时,要记住的最重要的事情是为自己设定合理的期望,当然,要保持水分充足。
期望#1:研究与工程
即使在阅读了其他人的经验之后,我仍然惊讶地发现,完成一篇论文会让你学到更多的工程知识,而不是研究知识。
许多在线课程将创建美丽的环境,在其中你可以玩深度学习概念。这些可以运行从预加载库的云实例到浏览器中完全集成的运动场的所有范围(想想fast . aivsuda city)。这对于降低新人的准入门槛是必要的,让他们在职业生涯的早期享受人工智能的全部荣耀。
There are many details safely hidden in online AI courses. Source: www.pexels.com
不幸的是,就像那些过度保护但用心良苦的父母的孩子一样,这种溺爱让小约翰尼对现实世界毫无准备。该由我们年轻人用实际知识来填补空白了。
我的意思是:
我不仅严重低估了这个项目需要的总时间,还错误地估计了工作的分配。我最初假设我会在周末完成这个实现,花大约五天的时间测试不同的假设,其中有 20/80 的时间。
实际上,我大约 40%的时间花在操作开销上,40%的时间花在实现、调试和测试模型架构上,最后 20%的时间花在实验上。
我花了两周半的时间,在工作前和工作后编码,在周末,这个项目总共花了我大约 60 个小时。我花了额外的 20 个小时处理模型部署,稍后会花更多的时间。
对于你的第一篇论文,假设你选择了一篇在你已经相当熟悉的学科中有点挑战性的论文,那么这种分布和时间承诺并不是不合理的。
总的来说:
- 为运作准备、**预算比你认为需要的更多的时间,尤其是你的第一篇论文。**利用这个机会提高你的软件工程技能和解决问题的能力。
- **在实施阶段,不断地在适当的检查点通过网络运行虚拟数据。**让自己相信输出是有意义的,如果不合理就修复它。绝对不要等到所有东西都构建好了才开始调试。
- 为了记录你的实验,以及追溯过去,做详细的笔记。一个快乐的副作用是能够深情地回顾你所取得的一切。
简而言之,好好计划,闻一闻玫瑰花香,多拍几张照片。
期望#2:再现性
复制危机是一种经常折磨社会和医学科学的疾病,因为进行人体实验研究是有风险的。当学者们无法复制同行的成果时,科学发现的引擎本身就会停止运转。
如果巨人没精打采,你不可能很好地站在他们的肩膀上。
发生这种情况有很多原因,包括软弱无力的研究标准,“发表或灭亡”的激励,以及在极限情况下,彻头彻尾的欺诈活动。即使好的科学是怀着最美好的愿望进行的,它也不总是一帆风顺的。在人工智能研究中,这通常以算法论文的形式出现,这些论文带有未记录的代码(如果发表的话),其性能会随着敏感的训练条件而波动。
这对你来说意味着什么,勇敢的旅行者,如果你的结果看起来不完全像你选择的论文,你一定不要气馁。我很快发现 作者经常会省略细节,因为它们不重要(它们很重要)或者因为它们非常明显(它们不重要)。
Authors can leave out seemingly unimportant, but crucial, details. Source: www.pexels.com
为了克服这一点,你不能仅仅将你的研究论文局限于原文。你必须汇集外部资源(GitHub、StackOverflow、你的邻居),并对影响每篇论文但并不总是显而易见的关键概念培养良好的直觉。这将帮助你智能地填补空白,以最终进行复制,并有望甚至超越深度学习算法的性能。
以下是作者没有回答的最常见的问题,但你仍然必须努力解决:
权重是如何初始化的?
在任何给定的层中,你都不希望你的权重相同、太大或太小。如果所有的权重彼此相同,你的更新(学习率乘以每个权重的导数)将具有相同的方向和幅度。
不太好。
不同的“神经元”需要有不同的变化,以便你的网络学习输入和输出之间的复杂关系——这被称为打破对称,也是单层神经网络有可能逼近任何连续函数的原因。本质上,这就是为什么深度学习这么有用。
您的权重值太大或太小将导致爆炸/消失梯度,最终使训练过程不稳定、缓慢或完全无效。如果你在高速公路上开车,坡度大的话,你会以每小时 120 英里的速度飞驰,坡度小的话,你会以每小时 5 英里的速度缓慢前行。
你要么错过退路,要么无聊而死…在这两种情况下,你都会被开罚单。
因此,为了得到完美的权重,我们必须确保它们是从一个合适的概率分布中抽取的。因为这种特别的洞察力是如此重要,许多真正聪明的人已经就这个主题写了完整的论文。因此, 当前的最佳实践是分别对 ReLUs(校正线性单位)或 tanh/sigmoid 激活函数使用 He 或 Xavier 初始化。
不幸的是,这些初始化方案本质上是随机的,因为权重是从分布中随机选择的。这意味着,即使神经网络可以用固定的初始权重和输入来确定,论文的复制也变得不确定。你的体重,以及随后你的结果,会略有不同,现在,我们将不得不接受这一点。
Initializations can be robust, but ultimately stochastic. Source: www.pexels.com
然而,使用上面的直觉,如果你的结果与论文的结果完全不同,你至少知道不要责怪权重。如果作者没有提到任何初始化过程,你也不会束手无策。
使用了哪些超参数?
有相当多的超参数在将深度学习算法带入生活中发挥着不可或缺的作用,其中大多数你会毫不费力地发现。
在大多数研究论文中,对隐藏层的大小和数量、正则化系数和其他建筑地标的描述与令人信服的理论基础讨论无缝地交织在一起。
这些 设计 超参数为您提供了成功实施的蓝图,但不会带您走得更远。为了配合论文的表现,你必须寻找不同种类的 训练 超参数。
这是你开始出汗的地方。
学习率在哪里?批量大小,时代?超越传统的超参数,使用了什么优化器?数据集中的训练和测试拆分是什么?
对于那些满足于仅仅跟上人工智能世界的最新进展的人来说,这些细节是无关紧要的。“我就看看报纸,然后继续前进!”他们会说,“更好的是,有没有我可以看一下的摘要?”
但对于那些头发花白、想要追逐疯狂、让现实屈从于我们意志的少数人来说,这些特征至关重要。你的目标不仅仅是简单地理解这些论文,而是将它们重新组合在一起,甚至是在它们成功的基础上再接再厉。
一种方法是在互联网上搜寻这些训练超参数。您可以在 GitHub 上找到相关的实现,并提取相关的值。不幸的是,涉水通过互联网代码可以非常令人沮丧和误导。
更好的策略是从基本原则开始,确定最佳实践,并磨练您对这些反复出现的主题的直觉:
**批量:**32 左右从开始。使用较少的训练样本来更新权重会产生一个“嘈杂的”梯度信号。与直觉相反,这种噪音对你的网络产生了一种规则效应,允许它很好地推广到看不见的测试数据。
Source: www.twitter.com
**历元数:**这个很简单。从足够多的历元开始(完全遍历您的数据),当验证错误开始变得更糟时停止训练。假设你一直在定期保存你的体重,选择那些给你最低验证损失的——这就是所谓的提前停止。
**训练/验证/测试集:**一个好的经验法则是将你的数据集分成 80/10/10。然而,你拥有的数据点越少,你投入训练的数据点就越多,所以这个比例是可变的。对数据如何在这些不同的集合中分布持怀疑态度并不是一个坏主意,但是如果你的论文使用了一个众所周知的基准,那么细节很可能已经提前为你处理好了。
**优化器:**与 AdamW ,Adam的更好变体。基本原理是选择一个优化器,该优化器使用梯度信号的一部分,根据它们的“需要”更新各个参数。想想社会主义。AdamW 将这种自适应更新与动量相结合,动量是先前梯度的移动平均。这可以智能地改变参数,同时防止当前批次的损耗大幅波动。
**学习率:**这是一个系数,优化器将其与梯度相乘以抑制误差信号(在 AdamW 的情况下是自适应的)。首先,使用任何学习率(lr)探测器获得最大和最小值。然后使用周期策略来调整你的学习速度和动力。这里的直觉是,在慢慢收敛到最佳参数之前,网络将在开始时快速探索解决方案。
再现性对科学进步非常重要,所以当人工智能研究人员让其他人更难跟随他们的脚步时,这是一种耻辱。掌握以上这些概念会给你前进的信心,即使你论文的作者没有明确说明训练程序。
更重要的是,你会注意到观点的转变,不再只是阅读一篇论文并思考,“这很有道理”,而是“我同意这个观点”或“我会用不同的方式来做这件事”。完成一篇研究论文将从一项纯粹的教育活动演变成同行评议。
Welcome to the scientific enterprise! Source: www.pexels.com
期望#3:个人舒适
虽然这无疑将是一次有趣的经历,但是您可以做一些事情来确保您的第一次实现之旅进展顺利。
- **选择培训时间在 5 小时以下的纸张。**这可能有助于减少您在第一次实施上花费的资金(取决于您运行的 GPU 实例)。更重要的是,后续训练之间更快的反馈循环可以让你快速实验,并且不会在出现问题时摧毁你的自尊。
- **从小处着手,逐步积累,直至全面实施。**绝对不要试图构建论文的完整架构,却发现自己犯了一些小错误,导致你的网络无法访问。解决这个问题的方法是一点一点地构建,通过网络运行一批数据,并说服自己事情是有意义的。在继续前进之前,解决你为自己构建的更简单的问题。
- 等到原来的结果接近可复制的时候再做实验。我一让事情运转起来,就开始运行迷你实验来戳戳我的网络,最终观察它的行为。这不一定是一件坏事,因为它可能会帮助您发展直觉,但是这些直觉与您的实现应该如何工作没有关系。所以先复制,后实验。
- 确保选择一个你知道一些或愿意探索的、启发智力的话题。我选择复制 NLP 上的一篇论文的结果,我觉得这很有趣,也与我的工作相关。这让我即使在材料具有挑战性的时候也能保持专注,我甚至能够将我学到的知识应用到工作中的算法中。
结束语
你是应该先一头扎进去,边走边学,还是应该在 试图完成一篇研究论文之前 尽你所能地学习?答案真的取决于你为什么要这么做,你希望从这次经历中得到什么。
不管你的策略是什么,重要的是要认识到,你的问题的答案并不总是在论文本身,当作者不明确时,概念性的理解可以帮助填补空白。或许,在你上船之前,一条让你具备基本知识的中间道路,会给你毫发无损地浮出水面的最佳机会。
到目前为止,我已经避免引用我的实际实现,因为这个讨论主要是关于动机和期望。然而,如果你感兴趣,我实现了 IBM 的双边多视角匹配模型,这是我第一次尝试复制深度学习算法。你可以在 GitHub 上找到我的实验、代码和测试。所有的东西都有详细的文档记录,而且我让事情变得非常容易理解。挖的开心!
关于复制深度学习论文的性能,当然还有更多可以说的(工具、调试策略、测量实验等)。),也许我会在以后的文章中深入探讨其中的一些。但是现在,希望你发现这篇文章对计划你的冒险有指导意义,并且理解在你的旅途中等待你的一些惊喜(愉快的或其他的)。
所以,勇敢的旅行者,保持安全但好奇,轻装上阵,记得把一切都带进去!一路顺风!
Bon Voyage! Source: www.pexels.com
将预训练嵌入空间投影到 KDE 混合空间的小数据集假设检验
一种主要用于通过面向领域的信息转换来帮助快速原型化、引导主题建模、假设检验、概念验证的方法。
文本分类任务通常需要高样本数和精细的语义可变性来进行可靠的建模。在许多情况下,手头的数据在样本计数、类别的过度偏斜和低可变性(即词汇多样性和语义)方面都是不够的。在这篇文章中,我将介绍一种新颖的方法来克服这些常见的障碍。这种方法的目的主要是帮助快速原型制作、引导主题建模、假设检验、概念验证(POC),甚至是创建最小可行产品(MVP)。
该方法由以下步骤组成:
- 加载我们的小数据集或主题词汇表(用于主题建模用例)
- 选择最合适的预训练嵌入
- 创建集群
- 最后,使用核密度估计(KDE)创建新的嵌入
第一步:加载我们的数据
我们从一个非常小的数据集开始。我们用的是施莱歇尔的寓言,其中的每一句话都是一个文档样本。
第二步:选择最佳嵌入空间
单词在本质上看起来是绝对的,但通过 Word2Vec & GloVe 等嵌入方法,它们现在可以被视为有限语义、密集表示的空间中的点。这种伪欧几里得空间表示可以极大地帮助简化语义任务。由于许多领域的数据短缺,通常的做法是从预先训练好的易于使用的嵌入模型开始。
作为一般规则,我们的域应该被完全表示。因此,所选择的嵌入应该包含尽可能多的单词,这些单词将是我们的数据的(主题建模)词汇。为了防止词汇之外(OOV)的单词,所选择的模型应该包含非常大量的记号。我通常选择最低的适应维度空间,因为更高的维度空间可以在来自嵌入空间的单词和我们的领域之间有更大的距离。换句话说,这可能导致聚类边界从我们的域向由预先训练的嵌入所表示的原始域倾斜。最后,我试图选择一个尽可能接近我的用例的嵌入空间,如图 1 所示。
Figure.1 — A t-SNE projection of our dataset overlaid on top of the chosen embedding space, sampled for visibility.
我用 Gensim 来训练一个 Word2Vec 模型。我通常在任何数据集上训练一个模型。然而,如下所示,最好是在一个大的外部数据集上训练,最好是与您自己的数据集混合。在这个实现中,空间维度是手动设置的,这给了我们关于聚类边界的优势。
一旦选择了嵌入空间,我们就根据我们的词汇表解析这个空间。以下假设有助于我们完成这项任务:
- 该空间是伪语义的,即,它是自动选择的,并且不受嵌入空间的真实上下文语义( Ker{} )的指导。它确保源数据在语义上尽可能均匀地分布单词距离,这有助于很好地定义聚类边界。
- 源数据应具有足够低的属性域偏差,以允许多个属性域基于所确定的距离。如前所述,这种假设似乎是一厢情愿的想法。
- 单词之间的差异由单个半径定义,即,在空间中没有方向依赖性。
下面的代码从预先训练的嵌入空间列表中选择最佳的编码空间,该列表由斯坦福大学提供,可从这里获得。请注意,以下过程使用标准的文本预处理方法,如文本清理、标点符号和停用词删除,然后是词干处理&词汇化。如下面的代码所示,可以使用 Gensim 包在您选择的任何数据集上创建其他嵌入文件,例如:
第三步:聚类
根据前面的假设,在步骤 2 中,我们如何选择正确的聚类算法、聚类的数量以及每个质心在空间中的位置?这些问题的答案在很大程度上依赖于领域。然而,如果您不确定如何添加您的领域的指导性约束或增强,我建议一种通用的、剥离的方法。一般来说,聚类的数量应该通过观察数据集来设置,因为任何转换的语义可分性应该与域本身中的未来任务相关。最小聚类数应该由您在未来任务中预见的最低类数来确定。例如,如果在不久的将来,您看到您的数据或域中的文本分类任务不超过 10 个类,那么最小分类计数应该设置为 10。然而,如果这个数字高于嵌入空间的维数,那么下限应该更大,并且在这一点上是未定义的。在任何情况下,它都不应该超过数据集的词汇或主题数,记住在这个用例中它是非常低的。
像聚类边界不确定性、每个聚类的 P 值分析、自适应阈值和有条件的聚类合并和分割等问题超出了本文的范围。
我们假设嵌入空间中的相邻单词在语义上足够接近,可以加入到某个语义簇中。为了定义聚类,我们需要确定一个距离度量。对于这个任务,让我们看看占据嵌入空间的令牌,并找出最接近的两个。设这两者之间的余弦距离为 Ro,则定义簇字邻接的最小距离为 R = Ro / 2 - ε,此时簇计数最大。换句话说,进行简单的实例到实例距离聚类来对单词进行分组。在主题建模的情况下,Ro 将是来自不同主题的最近单词之间的最小距离。
下面的代码使用选择的手套嵌入空间,使用 K=2 的最近邻对其进行聚类,并使用余弦相似度来确定最小距离。
前一种方法确保聚类将包含来自数据集的至少一个单词,同时记住在嵌入空间中总是存在未分配的单词。
将未分配的点(词)聚集成生成的簇的直接方法是标签传播/标签传播,如图 2 & 3 所示。
然而,由于较高的运行时间复杂性(代码#5),您可能希望使用更快且不太精确的方法,如线性 SVM(代码#6)。由于运行时复杂性问题,下面的代码比较了这两种方法。这一步是一个“蛮力”聚集,在未来的探索中,当我们的数据集预计会更丰富时,可能会产生不太理想的结果。
Figure.2 — A t-SNE projection after label-spreading of our dataset and a selection of samples from our chosen embedding space. please note that this is purely for illustrative purposes, as the real 2D display of the labeling would be similar to Figure 3.
Figure 3: A t-SNE projection after label-spreading, using a sample of tokens from our embedding space, colors represent the different labels. Please note that this is in a higher space compared to Figure 2 without dimensionality reduction.
让我们来关注一下为什么核心聚集之后是样本聚集是有意义的**。嗯,我们希望将嵌入限制在我们的数据锚/主题(单词)上。这需要语义上的接近。一旦实现了这一点,假设最外围的单词在未来的样本中不太可能出现。让我再强调一次——当我们从获得非常少的**数据开始,并且想要制作一个概念验证或基本产品(MVP)时,就会出现这种用例。
步骤 4:使用 KDE 创建新的嵌入空间
现在,所有的单词都被分配到一个聚类中,我们需要一个更有信息的表示,这将有助于未来的未知样本。由于嵌入空间是由语义接近度定义的,我们可以通过空间中该位置处每个聚类的概率密度函数(PDF)的密度来编码每个样本。
换句话说,在一个簇中位于密集区域而在另一个簇中密度较低的单词,将使用通过使用新的密度嵌入所投射的信息来展示这种行为。请记住,嵌入维数实际上是聚类计数,并且嵌入的顺序是相对于初始化该嵌入时提出的聚类来保持的。使用我们的小数据集,得到的投影可以在图 4 中看到。最后,下面的代码使用 KDE 创建一个新的嵌入。
Figure.4 — A t-SNE projection of the final density encoding map, which is a mixture model. label colors may have changed but they correspond to the label clusters as seen in Figure 2.
感谢 Ori Cohen 和**Adam Bali的宝贵批判、校对、编辑和评论。**
Natanel Davidovits
奇异问题解决者。数学建模、优化、计算机视觉、NLP/NLU &数据科学方面的专家,拥有十年的行业研究经验。
经济高效的机器学习项目提示
剧透:你不需要一个 24/7 运行的虚拟机一天处理 16 个请求。
Street art by Mike Mozart
你刚刚发布了一个机器学习项目。它可以是你刚起步的新产品,可以是客户演示的概念验证,也可以是丰富你的投资组合的个人项目。你不是在寻找一个生产级网站;你想完成工作。为了便宜。这样一些用户就可以测试你的产品。
如何经济高效地提供您的项目?
这篇文章是对这篇前一篇文章的后续和更新,在那篇文章中,我介绍了 raplyrics.eu ,这是一个使用 ML 生成说唱音乐歌词的文本生成网络应用。
到目前为止,这个项目已经提供了一年的笑点。我在此分享更新的架构,它使我们将云提供商的账单从每月 50 美元减少到每月不到 1 美元。
我使用这个项目作为例子,但是这里讨论的建议适用于每个具有灵活延迟需求的类似项目。
期待什么?
首先,我描述了服务的架构以及我们想要交付的内容。然后,我定义实现我们目标的可能方法。最后,我重点介绍了我们如何使用无服务器功能大幅降低计算成本。
服务剖析
First the user fetches the static assets, then locally executes the JS that calls the prediction server to generate lyrics.
- 首先,用户获取静态文件。
- 然后,用户在本地调用预测服务器。
- 最后,服务器返回预测。
在最初的解决方案和下面介绍的新解决方案之间,关注点的逻辑分离保持不变。我们只更新底层技术。
初始解决方案设计
What paying 600$ a year for a porftolio project feels like — Photo by Jp Valery on Unsplash
当我们开发 raplyrics 时,我们希望它非常简单。它最初是一个在我们的机器上构建和测试的项目。然后,我们把它推到了云端。
目前,有几种服务于机器学习模型的方法。以前,我们想弄脏自己的手,所以我们实施了我们的服务策略。
建议:不要开发你自己的机器学习模型服务框架——成熟的解决方案,如 tensorflow serving 已经存在。TF Serving 有详尽的文档,会比自己动手更有效率。
话虽如此,让我们回到我们的项目。我们把前面和后面分开;
- 客户端层是一个 Apache Http 服务器
- 服务器层是运行歌词生成模型的 Python flask 应用程序。
我们购买了一个领域模型,将我们的代码部署到一个 EC2 上,并且准备好为用户服务。
问题是
在免费试用期到期之前,一切都是乐趣和游戏。在最初的 12 个月之后,在 2019 年 9 月为 32 个用户服务时,这个单个项目的每月账单飙升至 ~每月 45/50 美元。
事实是,我们有一个 2GB Ram 的虚拟机,全天候运行,为几十个用户提供服务。
更新的解决方案设计
在免费试用之后,很明显我们处理这个项目的方式中有些地方出了问题。
我们服务的典型用户知道这个网站是一个个人项目;它设定了期望的水平。
典型的用户生成一打歌词,然后离开。
我们知道我们想要实现什么,服务一个两层架构,前端处理调用服务生成歌词的用户输入。前后松耦合。(仅指前端的后端端点)。
有哪些可能性,可能的如何?
列出选项
- A —将同一个项目部署到另一个提供免费积分的云提供商。重复一遍。
那是可能的。例如,如果你来自 AWS,GCP 的 300 美元信用可以让你运行一段时间。也许你只需要在有限的时间内为客户提供这个投资组合项目或概念证明。
我们想把我们的项目保留一段时间;选项 A 不太适合。
- B —为客户端层使用静态网站,通过对无服务器计算的 API 调用来服务请求。
什么是无服务器计算?
无服务器计算是一种按需提供后端服务的方法。服务器仍在使用,但从无服务器供应商那里获得后端服务的公司是根据使用情况收费的,而不是固定的带宽量或服务器数量。—来自 CloudFare
我们选择了选项 B,使用静态网站并在无服务器计算服务上公开我们的 API。选项 B 的缺点是增加了冷启动时的延迟。冷启动是无服务器计算服务的第一次运行;它通常需要比 W 臂启动更长的时间。
静态网站和运行中无服务器计算机
既然我们已经定义了我们想要如何去做,我们可以专注于技术的选择。
托管静态页面
存在多种静态托管解决方案。我们选择了 Netlify。很容易在最少的时间内完成这项工作。在 Netlify 上,使用自定义域名的基本主机和 SSL 证书是免费的。
用无服务器计算服务 API
每个云提供商都提供无服务器计算服务;我们选择了谷歌云及其云功能。
Google cloud 有一个教程是关于如何通过云功能来服务机器学习模型的。以本教程为基础,我们可以通过一点点重构来服务我们的模型。
每个云提供商倾向于以略微不同的方式处理他们如何提供云功能。谷歌云还提供基于 Dockerfile 的无服务器计算服务 Cloud Run。使用 Dockerfiles 可以更容易地将项目从一个云提供商转移到另一个云提供商。
冷启动延迟
对于冷启动,我们必须从桶中装载模型重量(150Mb)。然后,python 应用程序加载权重。在这些情况下,响应时间最长可达 40 秒。对于热启动,响应时间通常低于 2 秒。对于投资组合项目,我们可以接受这种成本/延迟权衡。
我们在前端添加了一些用户界面元素,使第一次预测更加清晰,这可能需要一些时间。
外卖食品
你不需要一个完整的生产规模来为你的小项目服务。以最具成本效益的解决方案为目标。
- 投资组合项目的延迟需求与生产服务的延迟需求不同。
- 静态网站和使用基于无服务器计算的 API 是服务于你的项目的一个划算的解决方案。
需要一些技巧来处理状态,有效地从网络加载资源,但是这样做的经济性值得一试。
感谢 Cyril 对这篇文章的深思熟虑的反馈。
资源
Raplyrics 源代码可在 GithHub 上获得。
托管静态网站
- 在亚马逊 S3 上托管一个静态网站, aws doc
- 您和您的项目的网站, GitHub 页面
- 托管一个静态网站, gcloud doc
- 在几秒钟内部署您的站点, netlify
无服务器计算服务
- AWS——运行代码时不考虑 AWS 上的服务器,
- Google Cloud——事件驱动的无服务器计算平台, gcloud functions
- Azure——事件驱动的无服务器计算, Azure functions
- 阿里巴巴——全托管无服务器运行环境,函数计算
有效数据可视化的技巧
Photo by Carlos Muza on Unsplash
数据可视化具有很强的设计元素。考虑到领域、应用程序和受众的差异,很难围绕可视化数据的最佳方式建立一个结构。然而,肯定有错误的方法!我遇到过许多这样的例子,它们是这篇文章背后的驱动力。
在这篇文章中,我想分享一些有用的技巧来帮助你避免视觉上的失误。
1.选择正确的视觉效果
这个可能看起来太明显了!但是我看到一些人试图毫无理由地展示他们艺术的一面…
永远记住形式跟随功能——一个视觉的目的应该是其设计的出发点**
问问你自己——你是在尝试比较价值、展示趋势、探索变量之间的分布或关系吗?然后根据你要传达的信息选择合适的视觉效果。考虑以下图表。基础数据集包含产品投诉/缺陷。我们试图用缺陷率 ppm(百万分率)来显示需要注意的产品。条形图是显示这些数据的简单而有效的方法。树状图和气泡图的一个缺点是要求读者比较面积而不是高度,这在视觉上很费力。
Choosing the right visual based on it’s function
2.琐碎的很多,但重要的很少(数据点)
不要只是在数据集上拍一张照片。分析您的数据,并将其转化为受众可以理解的信息“金块”。
旁边的图显示了过程变量的时间序列图。线 A-A '显示了周期和振幅都改变的时间点,这是问题的开始,最终导致其值在几个周期后突然下降。哪个图表能更好地揭示这种洞察力?
顶部的图表只是将数据转化为视觉效果,而底部的图表“调节”数据以提供洞察力。
假设我们想按项目 ID 显示年度总支出。有 41 个独特的类别。左边的图表看起来很拥挤,所有的类别都挤在里面。更好的显示方式是显示前 5 个类别,并将其余类别合并到“其他”类别中。
Suppressing the “noise” in the data
此外,请注意,过滤掉“其他”类别可能会夸大饼图的面积,或者改变总计算的百分比。这可能会产生误导!在显示百分比时,一定要确保它们加起来是 100%,或者解释为什么以及排除了什么。
3.数字不会说谎,但俗人会说谎!
视觉效果应该反映现实,而不是扭曲现实。图表的格式非常重要,因为它为观众建立了一个参考框架。
在下面的例子中,在 6 个月的时间里,一个过程的产量从 56%增加到 67%。左侧的图表试图夸大改进,将 y 轴格式化为从 50%开始。后一种视觉描绘了一幅精确的画面,其中 y 轴从 0 开始,并且还包括一条球门线。
Figures don’t lie…liars figure!
4.明智地使用颜色
应该使用颜色来添加更多信息或突出视觉效果中的关键数据点。在所有其他情况下,这是多余的和分散注意力的。
这篇由丽莎·夏洛特·罗斯特撰写的文章给出了一些选择视觉效果配色方案时需要考虑的要点。我还推荐试试由苏西·卢和以利亚·米克斯开发的工具。
5.与功能相比,美观有多重要?
如今可用的可视化工具让我们只需点击几下鼠标就能创造出最令人惊叹的丰富视觉效果。然而,过度渲染美学元素可能会分散人们对视觉效果关键信息的注意力。精益理念中的七种浪费之一是“过度加工”。
还有其他可以防止可视化失误的技巧吗?欢迎在下面评论!
信息图能讲述数据故事吗?
Author
创建第一个信息图的想法和技巧
让每个人都专注于你的数据分析是一项挑战。您面临着各种各样的挑战:糟糕的数据素养、不感兴趣,甚至注意力持续时间的缩短。
您是否考虑过在数据通信中添加信息图表?
这种技术以一种视觉上令人兴奋的方式讲述了一个数据故事,让用户按照自己的节奏消化数据。让我们回顾一些信息图示例来寻找灵感,然后是一些入门提示。
什么是信息图?
信息图(或信息图)用图像、数据可视化和文本讲述一个故事。这种技术有很多优点,但我喜欢它,因为它很容易抓住注意力持续下降的观众。信息图表可以让你将大量的数据浓缩成易于理解的片段。你可以把它看作是仪表盘和数据故事的结合。这些作品讲述了一个关于所选主题的快速而清晰的故事。
在广告中使用信息图表,或在社交媒体上分享,以吸引人们对你的分析的注意。一个设计良好的信息图的视觉本质能够抓住浏览者的注意力。我认为这种方法比其他数据表示方法更随意。这是一种有趣的方式来打开一个话题进行更多的探索。
但是,您也可以在内部使用这种技术!也许你有一个重点领域,你想突出。客户服务部门可能会显示客户在过去 30 天内致电的前 5 个原因,并突出显示有关这些电话的一些关键事实。
精明的数据分析师可以与工程或产品管理团队分享该信息图,以帮助他们了解他们如何影响客户。
了解信息图表类型
Travis Murphy 在他的书《SAS 支持的信息图表:商业报告的数据可视化技术 中描述了两种信息图表:艺术和商业。
艺术信息图 特色图片和极简文字。你经常会在厨房的墙上或青少年卧室的天花板上发现这些信息图表。想想展示各种可食用花卉或福特野马多年来如何变化的海报。艺术信息图是高度视觉化的。
你用你的眼睛去学习和比较什么是相似的,什么是不同的。
下面这张由 Cathryn Lavery 设计的信息图展示了蝙蝠侠标志这些年来是如何变化的。令人好奇的是,这个标志是如何变化的,却又保持不变。
快速消息的信息图表
SAS 用这张信息图解释了 SAS Viya 的主要特点。他们在社交媒体上使用了这张信息图,并在 SAS 全球论坛上发布。虽然它有一个商业主题,但它的内容并不侧重于分享统计数据,而是允许观众探索 SAS Viya 产品的功能和用途。
信息图中的信息很简单,该产品有四个主要特点。该产品非常适合多种分析角色。他们传达的信息是这款产品非常灵活,适用于多种角色。
它使用公司主题的颜色来传达信息,并引导你的视线向下。
商业信息图比艺术信息图更加结构化。这些更像 web 报告,因为它们包含统计数据和数据可视化。这些信息图表用数据讲述了一个故事。
信息图讲述数据故事
下图显示了一个业务信息图。你可以看到图片和图形比文字更流行。
生命统计数据被用来用一条短消息来启发观众。
考虑一下 SAS 的这张信息图,它可以帮助您了解数据技能差距。它讲述了一个关于问题的故事,以及他们需要什么来解决问题。
他们会带您浏览每个元素,并提供支持信息和有趣的数据可视化以供探索。
信息图表带领观众踏上探索之旅
罗伯特·艾利森的下面这张信息图从定量的角度帮助你理解《星际迷航》。
这里的艺术品是色块。请注意,该信息图引用了原始电视连续剧的颜色和字体。
这些字体与片头序列中使用的字体相同。颜色和原来 60 年代的船员穿的一样。颜色传达了如此强烈的信息!
所有有用的信息图表都需要一个外卖,例如作为一名红衫军成员是危险的,柯克船长不仅仅是一个人类女士的男人。这些数据引起了激烈的争论——成为一名黄衫军是危险的。]但是饼状图在这里看起来很好吃!
在下面的信息图中, Falko Schulz 用 SAS 视觉分析软件讲述了珠穆朗玛峰的故事。他使用各种图片、数据对象和文本字段来引导您浏览数据。你的眼睛滑过数据,发现了一些小细节。
这张信息图是让人们回答下一个问题的绝佳方式:“如果这项活动如此危险,人们为什么要这么做?”你注意到底部的折线图了吗?这是一个非常棒的信息图表!
想象一下,如果我试图用幻灯片给你同样的信息!虽然这个话题很有趣(尽管有点病态),但是想想看,如果你看完了 25 张幻灯片,看完了我对喜马拉雅探险队数据库的分析,或者翻阅了 PDF 文件,你会怎么想?
通过使用信息图,您的受众可以探索信息的详细程度。也许知道下山比登顶更危险就足够了。
也许当你得知雪崩比坠落更致命时会感到惊讶。
当你查看上山路线时,你可以清楚地看到登山者的路径覆盖了一个雪崩的完美地点。然而,Falko 让你发现了那种洞察力。
信息图表娱乐并吸引观众
在创建我的信息图时,我想突出一个让我好奇的主题——鲨鱼。虽然我同意这是一个性感的话题,但这张信息图可以让你说服自己,鲨鱼事件是否如此普遍或致命。
我在 SAS 可视化分析 8.3 中创建了这个信息图。我们想用一个数据故事来庆祝鲨鱼周。这张地图看起来很漂亮。
创建有效信息图的技巧
与其他数据交流方式相比,信息图表更能挑战你的编辑、设计和布局技能。对于那些可能更擅长机器学习的人来说,这是一种全新的设计体验。
为了取得成功,请牢记这些最佳实践:
- 确定主要信息和支持该信息的数据事实。由于篇幅有限,每个页面元素都很重要。此外,用户还可以从图形在页面上的显示方式中得出结论。
- 视觉元素越大,用户认为信息越重要。
- 在纸上创建页面元素的布局。你可以用纸和笔记下你的想法,并预先组织好你的事实。
- 考虑每个元素给故事增加了什么,以及它如何引导用户浏览故事。
- 你的图形或网页设计技能在这个练习中很有用。如果你没有接受过培训,可以考虑阅读设计原则。互联网上有许多免费的课程,如 Udemy 、 Skillshare 以及其他类似的网站。
- 如果你在 SAS Visual Analytics 中创建信息图,使用 Travis Murphy 的这些有用的提示。如果你用的是基地 SAS,那么用 Robert Allison 的流程。
如果你第一次尝试制作信息图感觉有点僵硬,你一定不要气馁;你的技能会随着练习而提高。
当您希望快速传达信息并吸引观众时,请将信息图表添加到您的数据通信组合中。信息图表允许你的用户以一种有趣而悠闲的方式探索数据。这种技术让用户问下一个问题,同时仍然有一些重要的收获。
最初在 https://www.zencos.com 出版。
IPython Alias 预加载您喜爱的模块并激活自动加载
用一种非常简单的方法获得您自己的强大的 IPython
TL;速度三角形定位法(dead reckoning)
您只需复制下面的别名,并将其粘贴到您的.bashrc
、.zshrc
或某个配置文件中:
alias ipy="ipython --no-confirm-exit --no-banner --quick --InteractiveShellApp.extensions=\"['autoreload']\" --InteractiveShellApp.exec_lines=\"['%autoreload 2', 'import os,sys']\""
Photo by Geetanjal Khanna on Unsplash
我经常使用 IPython 来开发我的库或做一些研究,因为 IPython 有如下非常好的特性:
- 运行常见的 shell 命令:ls、cp、rm 等。
- 此外,运行任何 shell 命令!(一些命令)。
- IPython 提供了许多神奇的命令 : run、debug、timeit 等
- 伟大的自动完成
- 在 Python 中预装你喜欢的模块
- 自动重装扩展
我想你已经知道 IPython 提供了一个很好的 Python 解释器,但是也知道你需要准备profile
来定制 IPython,比如预加载或者激活一个扩展。我觉得这有点麻烦。所以我在这篇文章中解释了预加载你喜欢的模块或者激活autoreload
而不需要配置文件的方法。
在 Python 中预装你喜欢的模块
当 IPython 启动时,您可以像下面这样执行 Python 代码。在这种情况下,我预加载 os 和 sys 模块。
ipython --InteractiveShellApp.exec_lines="['import os,sys']"
如果你想预装 PyTorch 或 Tensorflow,这里是你想要的:
ipython --InteractiveShellApp.exec_lines="['import torch', 'import tensorflow as tf']"
激活自动重新装入扩展
autoreload
在执行代码之前自动重新加载模块。您可以按如下方式激活autoreload
:
ipython --InteractiveShellApp.extensions="['autoreload']" --InteractiveShellApp.exec_lines="['%autoreload 2']"
通过使用--InteractiveShellApp.extensoins
,可以加载 IPython 扩展。要启用autoreload
,需要执行%autoreload 2
。
其他提示
您可以删除横幅:
ipython --no-banner
此外,您还可以删除确认:
ipython --no-confirm-exit
获取您自己的强大 IPython
我建议您将所有内容组合起来,并使其成为别名:
alias ipy="ipython --no-confirm-exit --no-banner --quick --InteractiveShellApp.extensions=\"['autoreload']\" --InteractiveShellApp.exec_lines=\"['%autoreload 2', 'import os,sys']\""
注意--quick
是一个无需配置文件的快速启动选项。现在,您可以轻松地以自己喜欢的方式更改这个别名。您可能想要加载额外的模块,如PyTorch
、TensorFlow
、Keras
、scikit-learn
等等。
我的帖子到此为止。在这篇文章中,我解释了用一种非常简单的方法定制你的 IPython 并使它强大的方法。这篇文章是写给那些不喜欢建立自己的配置文件,并且想要一个更简单的方法来做这件事的人的。希望我的帖子能帮到你。如果你想更多地定制 IPython 或使用 Jupyter,请查看这篇精彩的帖子:如何将你最喜欢的库自动导入 IPython 或 Jupyter 笔记本。感谢您的阅读。
关于与企业一起定义数据科学项目范围的 3 个提示
如何以数据科学家的身份与企业交流
永远不要因为在错误的时间把错误的东西送给错误的人而陷入麻烦
作为一名数据科学家,你希望得到一份能让你做酷事情的工作——大数据、大机器(或者像成年人一样的云)和深度神经网络。当你意识到你的模型、你的项目经理的时间表和你的利益相关者的期望之间的不匹配时,现实很快就来了。他们(通常)需要的不是一个 128 层的 ResNet,而是一个简单的 select & group by 查询,提供可操作的洞察力。
你的新工作已经进行了两个月,你的闪亮模型刚刚被束之高阁,嘟囔着:“什么是可操作的见解。谁的洞察力?用什么行动?”
本文概述了在(不)定义数据科学项目范围中的常见陷阱,以及如何分散或防止这些情况发生的技巧和框架。
陷阱 1:范围太广或未定义
使用数据主导、数据驱动和数据优先的方法的人数激增,分析领域的每个人都遇到过这一(或)著名的业务需求:
“告诉我数据怎么说。”
不要误会我;我非常支持根据历史模式和精心设计的预测做出决策。然而,作为数据从业者,我们也知道有各种各样的数据类型:通常管理不善的内部数据,每个人都认为是分析圣杯的外部数据和什么都不说的内向数据。
操纵数据的表示来传达您想要发送的信息甚至容易得令人不安。例如,通过稍微挤压图表,你的收入增长突然看起来像5 倍,而不是 0.5 倍。更糟糕的是,通过不提出正确的问题和开发强有力的假设测试,数据甚至可以用来增强你现有的信念。
范围宽泛且不明确的危险就在于此。这对于你先前的信念来说是非常主观的,大多数时候,企业和科学家在理解和解释不同的事情。
陷阱 2:范围过窄或定义过窄
与过于宽泛的范围相反,你可能还会遇到一些非常热情的利益相关者,他们在哈佛商业评论上阅读了一些关于客户流失建模或客户细分的文章,并要求你构建一些非常具体的东西。
当这种情况发生时,我们的第一反应往往是进入求解模式,开始幻想两周前你读过的那篇论文中的卷积 LSTM。然后我们开始卷起袖子,掰着手指头想…
嘿,我知道怎么做。
在经历了血、汗和泪之后,你终于有东西可以展示了。在演示会上,利益相关者看着你,你回头看着他,期望得到奖金或至少拍拍他的背,但他困惑地问道:
好吧,那很酷,但是仪表板在哪里?
等等什么?原来他从来没有想要一个客户流失模型。
他想要的只是一个仪表板,上面有合同将在未来三个月内到期的客户。其目的不是预测谁会续签合同,而是让一线呼叫中心跟踪他们与这些客户的接触情况。
你站起来,收拾好你构造精美、优化高效的模型,然后走出来。
现在深呼吸(不要打你的电脑)
让我们在这里暂停一下,回想一下这是什么时候发生在你身上的。看看下面两个与范围过宽或过窄相关的选项。如果你能回到过去选一个,你会怎么做?
这里(或者永远)没有正确或错误的答案,这些都是优秀的敏捷方法,可以在需求不明确的时候使用。
但是,我希望你做的不是专注于如何缩小或重新定义范围,而是提出更多的问题。无论是太宽泛还是太狭窄的场景都假设问题是正确的,并且是我们不舒服的解决方案,但是如果问题甚至不是真实的呢?如果没有问足够多的问题,你可能会无意中掩盖症状,掩盖根本原因。
因此,如果你从这篇文章中拿走一样东西,我希望你再次记住这一点:企业不知道他们想要什么。
你的工作是帮助企业找到他们想要的东西。
技巧 1:找出企业真正想要的是什么
事实是,我们的大多数业务利益相关者不了解数据科学项目的可能性。在构建他们的问题时,通常有很多假设但没有明确地说出来,所以从表面上接受他们的请求会导致无数次的范围变化。
作为你所在领域的专家,你的工作是引导企业通过术语的丛林,将他们的征服世界的神奇按钮业务问题转化为可以解决的问题,数学上,以及他们的数据。
想象一下,在对想要的可操作的见解进行一些探究之后,你发现他们所关注的是**【缓慢增长】**。咻,至少它限制了对与成长相关的神奇事物的探索。然后,您可以提供一些与增长相关的出色见解,如收购成本和流失率。
不幸的是,读懂另一个人的想法并不容易,但幸运的是,你可以使用一些试探性的问题:
1.是什么让你夜不能寐?
2。你想证明或反驳什么?
3。你的直觉告诉你什么?
4。如果你有预算做好一件事,那会是什么?
技巧 2:不要只是被抛给一个问题,帮助定义它
作为一个技术人员,你对解决问题的热情定义了你。与此同时,这个特征最终会阻碍你解决真正的问题。幸运的是,帮助定义问题不仅可以防止范围的改变,而且非常有趣*。*
有时,问题不是立即可以解决的,需要多个步骤才能实现——但同样,企业可能不知道这一点。与其被指责为你的流失模型无助于减少流失,你可以掌握自己的事情,为自己(或你的利益相关者,因为你应该更了解)构建一个路线图。
这是你释放所有创造力的时候,你又回到了五岁:
1.你为什么想知道是谁取消了他们的合同?
2。如果你知道这些,你会怎么做?
3。但是为什么,为什么为什么?
有大量的方法可以帮助你发现真正的问题(例如 5 个为什么、根本原因分析和石川(鱼骨)图),所以我将跳过这篇文章来节省我们的时间。我鼓励你们研究并熟悉它们。
最后,我想分享我用来揭示我的利益相关者有什么问题的研讨会框架。
研讨会框架:定义用例并确定优先级
今年早些时候,我参与了一个“帮助我们创建 XYZ 滚动预报”的项目。这似乎是一个直接的时间序列预测问题,但当试图最终确定需求时,我意识到每个人都认为这种“预测模型”将提供巨大的差距。它包括每日预报(每月的时间序列?!)到用户必须执行的具体行动要求,以缩小差距。
我召集了一大群来自不同业务部门的人,开了一个研讨会,了解他们真正想要的是什么。最后,每个人都同意他们需要一个时间序列预测模型,但是他们现在想要的是一个场景规划工具。
这个 3 小时的研讨会让我发现:
让每个人都发泄出来
这是你问每个人他们生活中有什么问题的部分。这些可以是大的或小的,长期的或短期的,他们观察到的或他们经历的,他们想要的或他们需要的。
The actual screenshot from the actual workshop #provingithappened
将他们所有的愤怒和挫折分成不同的主题,并向听众总结。
第二步:让每个人都思考
最重要的部分不是他们想要什么,而是 为什么 他们想要。第一步的全部目的是唤醒他们对世界上所有问题的记忆。练习的这一部分迫使他们理解为什么他们会感到沮丧。他们想实现却无法实现的是什么?
第三步:让每个人都为此而战
然而可悲的是,有些梦想注定永远是梦想,企业自己认识到这一点是至关重要的。通常,不同的利益相关者在奖励水平上会有分歧:信贷团队想要所有最有可能违约的用户,呼叫中心想要最有可能升级的用户,而财务团队想要终身价值最高的用户。你可以解释为什么有些项目很难实现,但是对于回报,商业利益相关者必须自己解决。
最后:有一个路线图。
有一个路线图,如果不是为业务,那么为自己。
当然,需求总是会变化的,但是具有前瞻性的观点有助于让每个人都保持在正确的轨道上。最终的目标往往是一条漫长而艰辛的路,所以眼睛盯着球,放开那 0.2%的准确度。
有时候在现实生活中,够好就够好了
如果你喜欢这篇文章,我将在接下来的几周发表以下内容:
- 如何用业务管理数据科学项目范围
- 如何向企业传达数据科学项目进展
我会确保这些帖子出来后我会超链接它们:)