理论在数据科学中的重要性

2023-02-04 13:16 350 阅读 ID:755
磐创AI
磐创AI

四个例子说明了为什么数据科学家知道他们在做什么至关重要。

数据科学是一门庞大的、定义不清的、不断变化的学科,在过去十年中已经基本民主化。只要对编程有一个基本的了解,并且能够访问YouTube,任何人都可以在不了解算法如何工作的情况下地实现算法。

虽然有很多资源涵盖了数据科学概念的理论基础,但很少有人证明为什么拥有这些基础在实践中很重要。本文给出了通过理解理论可以避免的数据科学“陷阱”的四个例子。

示例1:单变量特征选择

问题

特征选择的过程虽然很关键,但往往会令人费解和困惑。在回归设置中,最常见的第一步是分析目标和潜在特征之间的相关性。第一个例子将说明丢弃具有小相关性的特征的危险。

假设我们得到具有三个潜在特征(x1、x2和x3)和连续目标(y)的以下数据集:

由于计算限制和利益相关者的要求,我们只想选择对预测至关重要的特征。也许一个直观的开始是查看相关矩阵:

我们立即注意到x1与y具有中等相关性,而x2和x3几乎没有相关性。

错误的是,我们断定x1是唯一可用的有用特征,我们将其放入随机森林。该模型适用于训练集,并在测试集上进行评估:

# Create training and testing sets
x_train, x_test, y_train, y_test = train_test_split(data[['x1']], data['y'], test_size=0.20)

# Fit a random forest using only x1 as input
model = RandomForestRegressor(n_estimators=500, max_depth=10)
model.fit(x_train.values.reshape(-1,1), y_train)

# Evaluate predictions on the test set
preds = model.predict(x_test)

fig, ax = create_fig()
ax.scatter(y_test, preds)
ax.set_xlabel('Actual')
ax.set_ylabel('Predicted')
ax.set_title('Actual vs Predicted Target using $X1$ as a Feature')

print(f'MAPE between actual and predicted: {mean_absolute_percentage_error(y_test, preds)}')
print(f'R-Squared between actual and predicted: {r2_score(y_test, preds)}')

毫不奇怪,该模型在测试集上的表现很差——我们(错误地)在看到数据集中的相关性很低之后就预料到了这一点。

为了搞笑,我们决定将x2和x3放入模型中,并查看测试性能:

# Create training and testing sets
x_train, x_test, y_train, y_test = train_test_split(data[['x1','x2','x3']], data['y'], test_size=0.20)

# Fit random forest using all features
model = RandomForestRegressor(n_estimators=500, max_depth=6)
model.fit(x_train, y_train)

# Evaluate predictions on test set
preds = model.predict(x_test)

fig, ax = create_fig()
ax.scatter(y_test, preds)
ax.set_xlabel('Actual')
ax.set_ylabel('Predicted')
ax.set_title('Actual vs Predicted Target using all Features')

print(f'MAPE between actual and predicted: {mean_absolute_percentage_error(y_test, preds)}')
print(f'R-Squared between actual and predicted: {r2_score(y_test, preds)}')

令人惊讶的是,通过添加两个与目标无关的特征,我们观察到测试性能的天文数字改善(+0.63 R-Squared,-5.18%平均绝对百分比误差)。这怎么可能?

解决方案

这是一个经典的例子,说明了为什么单变量特征重要性度量(如相关性)可能具有欺骗性。在该数据集中,输出通过以下等式与输入相关:

数据集是在python中创建的,代码如下:

Create a fake dataset
n = 50000

x1 = np.random.normal(size=n)
x2 = np.random.normal(size=n)
x3 = np.random.normal(size=n)

y = x1*x2 + x3**2 + x1

data = pd.DataFrame({'x1':x1, 'x2':x2, 'x3':x3, 'y':y})

因为皮尔逊相关只能测量线性关系的大小和方向,所以它无法识别x1和x2之间的相互作用,或x3的二次关系。

对于这个玩具示例,我们可能会完全绕过特征选择过程,因为只有三个特征可用。然而,可以想象这一原理将如何应用于高维数据集。最终,特征选择过程需要考虑非线性和相互作用,这些非线性和相互影响不能总是由单变量度量来解释。

示例2:基于树的模型预测域外数据

问题

在示例1中,读者可能已经注意到目标(y)和特征(x1、x2和x3)之间的关系是确定的。也就是说,我们可以精确计算给定特征集的目标值。然而,即使将所有特征输入到随机森林中,结果也远不够完美:

我们知道随机森林能够处理非线性关系,那么为什么这个模型不能完美地预测目标呢?

解决方案

问题是测试集包含训练集域之外的特征。因此,测试集中的相应目标值在训练集的域之外。通过查看目标的摘要统计数据可以更好地看出这一点:

虽然这两种分布之间的差异是微不足道的(并且没有统计学意义),但它们足以欺骗随机森林。标准回归树以及随后的随机森林的输出由目标的最大值和最小值限定。这在许多应用程序中是可以的,因为我们通常不希望我们的模型外推到域外示例。然而,在物理学和许多其他科学领域,域外往往是目标。

在实践中有一些解决这个问题的方法,但对于这个例子,我们演示了线性模型如何提供完美的测试性能。让我们稍微作弊,假装我们强烈怀疑目标与示例1中给出的等式中的特征有关。我们将以下特征添加到训练和测试集中:

# Add quadratic and interaction features
x_train['x1x2'] = x_train['x1']*x_train['x2']
x_train['x3^2'] = x_train['x3']**2

x_test['x1x2'] = x_test['x1']*x_test['x2']
x_test['x3^2'] = x_test['x3']**2

然后将这些新特征输入到线性模型中,并在测试集上评估结果:

# Fit linear model
model = LinearRegression()
model.fit(x_train, y_train)

# Evaluate predictions on test set
preds = model.predict(x_test)

fig, ax = create_fig()
ax.scatter(y_test, preds)
ax.set_xlabel('Actual')
ax.set_ylabel('Predicted')
ax.set_title('Actual vs Predicted Target using all Features')

print(f'MAPE between actual and predicted: {mean_absolute_percentage_error(y_test, preds)}')
print(f'R-Squared between actual and predicted: {r2_score(y_test, preds)}')

线性模型能够完美地进行域外推理,因为它简单地将方程中的每个项乘以系数1。当然,这个例子是不现实的,因为我们事先知道了基本方程。然而,即使我们没有这些信息,我们也可以使用像符号回归这样的算法获得相同的结果:https://en.wikipedia.org/wiki/Symbolic_regression#:~:text=Symbolic%20regression%20%28SR%29%20is%20a,terms%20of%20accuracy%20and%20simplicity.

示例3-统计显著性与效应量

问题

我们为一家健身公司工作,该公司设计了两个肌肉锻炼项目,我们的老板问我们最新的项目是否有助于我们的客户在两个月内增加肌肉质量。自然地,我们观察两个项目获得的肌肉质量分布:

我们还查看了汇总统计数据:

我们很兴奋,因为新计划平均比旧计划增加了约.50磅。因为我们是数据科学家,我们觉得有必要检查统计意义。进行两个样本的z检验,以检查旧组获得的平均肌肉与新组获得的肌肉之间的差异是否不同。这个测试的p值是0.002,所以我们向老板展示了统计上有意义的发现并庆祝。该公司决定将这些结果推向市场,并为新计划收取更多费用——许多客户都会转投。

在新计划实施了两个多月后,该公司面临着巨大的投诉,因为客户抱怨他们的肌肉量没有老计划增加的那么多。我们的老板指责我们的错误。

解决方案

这里的问题是频率统计的核心——大样本量主导了统计显着性。这在许多倾向于使用小样本的介绍性统计课程中通常不会得到解决。我们可以通过查看当样本量趋于无穷大时检验统计量会发生什么,在数学上看到这个问题:

随着样本大小的增加,表达式的分母变为0,z统计量变为无穷大。当然,当测试统计量变为无穷大时,p值变为0。这意味着,如果样本量足够大,即使分布之间的最小差异也可能具有统计显着性。这就是我们需要效应量的地方。

统计测试的效应量试图测量观察结果的真实强度,而与样本大小无关。这并不意味着不考虑样本量,但较大的样本量并不主导效应量的计算。Cohen’s d是一种常用的衡量均值差异测试效应大小的方法:

注意平方根内的分子和分母如何随样本大小线性增长。这使得我们可以通过相应的样本量来衡量每个样本的标准差,而不会出现问题。Cohen’s d已普遍接受用于确定效应量的阈值:

我们可以计算Cohen’s d:

# Compute effect size
sd_pooled = np.sqrt(((n1-1)*old_program.std()**2 + (n2-1)*new_program.std()**2) / (n1 + n2 - 2))

cohen_d = (new_program.mean() - old_program.mean()) / sd_pooled

这告诉我们,虽然我们观察到的差异在统计学上是显著的,但真正的影响可能很小。如果我们在分享我们的结果之前就知道了效应量,我们可能不会对新项目做出如此强烈的声明。

我们还可以通过查看考虑整个分布的统计数据来改进我们的分析,而不仅仅是平均值和标准差。例如,人口稳定性指数或科尔莫戈洛夫-斯米尔诺夫统计等统计数据对于比较任意分布之间的差异非常有用(即我们不必假设正态性)。这些统计数据还带有普遍接受的效应量阈值。

示例4-基于树的特征重要性

问题

在最后一个例子中,我们为一所大学工作,我们的任务是确定哪些学生可能毕业,哪些学生可能辍学。我们关心这个场景中特征的重要性,因为我们想知道哪些学生特征倾向于有助于毕业的可能性。我们首先读取数据集并进行一些预处理:

# Read in academic success data
data = pd.read_csv('Dropout_Academic Success - Sheet1.csv')

# Only include students that have graduated or dropped out
data = data[data['Target'] != 'Enrolled']

# Convert target to a binary value
data['Target'].replace({'Graduate':1, 'Dropout':0}, inplace=True)

我们只考虑已经毕业或辍学的学生。这个数据集有很多分类特征,因此我们决定构建一个Catboost分类器来预测学生是否会毕业。在此之前,我们需要确定分类特征:

cat_cols = ['Marital Status','Application mode','Course','Daytime/evening attendance', 'Previous qualification','Nationality',"Mother's qualification", "Father's qualification","Mother's occupation","Father's occupation",'Displaced', 'Educational special needs', 'Debtor','Tuition fees up to date','International']

接下来,创建添加了随机特征的训练和测试集。随机特征是高斯分布的样本,并用作比较其他特征的预测性的基准。

# Create train/test split
x,y = data.drop('Target', axis=1), data['Target']

# Add random variable to inputs to evaluate feature importance
x['rv'] = np.random.normal(size=len(x))

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.20)

我们将分类器拟合到训练数据,然后评估特征重要性:

# Evaluate feature importance
feature_importances = model.get_feature_importance()

feat_imp_df = pd.DataFrame({'Feature':model.feature_names_, 'Importance':feature_importances})
feat_imp_df.sort_values("Importance", ascending=False, inplace=True)

fig,ax = plt.subplots(figsize=(15,15))
sns.barplot(x=feat_imp_df['Importance'], y=feat_imp_df['Feature'])

令人震惊的是,只有七个特征的重要性排名高于随机特征。我们得出结论,我们的大多数特征是噪声,并将其从模型中删除。然而,在这样做之后,模型的性能会明显变差。这怎么可能?

解决方案

我们这里的陷阱是,我们不理解Catboost是如何计算特征重要性的。Catboost的默认重要性类型是“PredictionValuesChange”,它测量输入特征改变时模型输出的变化量。基于树的模型往往锁定高度粒度的特征(如我们添加的连续随机特征);伪分区是从这些噪声特征中学习的,这些噪声特征导致最终模型对它们敏感。这就是我们在这个例子中观察到的。该模型已经从训练集上的随机特征中学习了噪声,并在响应中改变其预测。

一个简单的解决方案是在Catboost中使用“LossFunctionChange”重要性类型。此重要性类型查看当从模型中排除特征时损失函数的变化程度。至关重要的是,这需要对测试集进行评估,这有效地揭示了随机特征没有预测能力。我们可以使用以下代码评估此特征重要性类型:

# Create catboost pool object
test_pool = Pool(x_test, y_test, cat_features=cat_features)

# Evaluate feature importance
feature_importances = model.get_feature_importance(data=test_pool, type='LossFunctionChange')

feat_imp_df = pd.DataFrame({'Feature':model.feature_names_, 'Importance':feature_importances})
feat_imp_df.sort_values("Importance", ascending=False, inplace=True)

fig,ax = plt.subplots(figsize=(10,10))
sns.barplot(x=feat_imp_df['Importance'], y=feat_imp_df['Feature'])

新的特征重要性图表显示,在测试集上,只有一个特征的表现比随机差。此外,我们看到随机特征会导致模型在包含时失去预测性(如预期)。这个特征重要性图表与我们的直觉吻合得更好,并证实了大多数特征都是预测性的。

最后

这四个例子虽然是表面上的,但却描述了缺乏理解会给数据科学家带来很多麻烦。关键的要点是,数据科学家在使用某个东西之前,应该对它的工作原理有一个扎实的(不一定是完美的)把握。同样重要的是知道在应用程序中使用哪些方法。

感谢阅读!

免责声明:作者保留权利,不代表本站立场。如想了解更多和作者有关的信息可以查看页面右侧作者信息卡片。
反馈
to-top--btn