Python 机器学习 基础 之 数据表示与特征工程 【单变量非线性变换 / 自动化特征选择/利用专家知识】的简单说明

news2025/4/5 21:06:53

Python 机器学习 基础 之 数据表示与特征工程 【单变量非线性变换 / 自动化特征选择/利用专家知识】的简单说明

目录

Python 机器学习 基础 之 数据表示与特征工程 【单变量非线性变换 / 自动化特征选择/利用专家知识】的简单说明

一、简单介绍

二、单变量非线性变换

三、自动化特征选择

四、利用专家知识

附录

一、参考文献


一、简单介绍

Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。Python是一种解释型脚本语言,可以应用于以下领域: Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫。

Python 机器学习是利用 Python 编程语言中的各种工具和库来实现机器学习算法和技术的过程。Python 是一种功能强大且易于学习和使用的编程语言,因此成为了机器学习领域的首选语言之一。Python 提供了丰富的机器学习库,如Scikit-learn、TensorFlow、Keras、PyTorch等,这些库包含了许多常用的机器学习算法和深度学习框架,使得开发者能够快速实现、测试和部署各种机器学习模型。

Python 机器学习涵盖了许多任务和技术,包括但不限于:

  1. 监督学习:包括分类、回归等任务。
  2. 无监督学习:如聚类、降维等。
  3. 半监督学习:结合了有监督和无监督学习的技术。
  4. 强化学习:通过与环境的交互学习来优化决策策略。
  5. 深度学习:利用深度神经网络进行学习和预测。

通过 Python 进行机器学习,开发者可以利用其丰富的工具和库来处理数据、构建模型、评估模型性能,并将模型部署到实际应用中。Python 的易用性和庞大的社区支持使得机器学习在各个领域都得到了广泛的应用和发展。

二、单变量非线性变换

在机器学习中的特征工程中,单变量非线性变换是一种将单个特征应用非线性函数的技术,以便提高模型性能或满足模型假设。这些变换有助于处理特征与目标变量之间的非线性关系。常见的单变量非线性变换包括对数变换、平方根变换、平方变换、指数变换等。

下面是一些常用的单变量非线性变换的示例:

1)示例:对数变换

对数变换常用于将具有右偏分布的数据拉近正态分布。它在处理正数数据时特别有用。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split

# 加载加州房价数据集
california = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, random_state=0)

# 选择一个特征进行对数变换
feature = X_train[:, 0]  # 'MedInc' 特征
log_feature = np.log(feature + 1)  # 加1以避免对数0的情况

# 绘制原始特征和对数变换后的特征
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.hist(feature, bins=30)
plt.title("Original Feature")
plt.subplot(1, 2, 2)
plt.hist(log_feature, bins=30)
plt.title("Log-transformed Feature")
plt.show()

2)示例:平方根变换

平方根变换用于减弱特征中较大的数值的影响。它在处理计数数据时特别有用。

sqrt_feature = np.sqrt(feature)

# 绘制原始特征和平方根变换后的特征
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.hist(feature, bins=30)
plt.title("Original Feature")
plt.subplot(1, 2, 2)
plt.hist(sqrt_feature, bins=30)
plt.title("Square-root-transformed Feature")
plt.show()

3)示例:平方变换

平方变换用于增加特征的非线性性。它在处理特征与目标之间具有二次关系的数据时有用。

square_feature = np.square(feature)

# 绘制原始特征和平方变换后的特征
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.hist(feature, bins=30)
plt.title("Original Feature")
plt.subplot(1, 2, 2)
plt.hist(square_feature, bins=30)
plt.title("Square-transformed Feature")
plt.show()

4)示例:指数变换

指数变换用于增强特征中较小的数值的影响。

exp_feature = np.exp(feature)

# 绘制原始特征和指数变换后的特征
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.hist(feature, bins=30)
plt.title("Original Feature")
plt.subplot(1, 2, 2)
plt.hist(exp_feature, bins=30)
plt.title("Exponential-transformed Feature")
plt.show()

5)将非线性变换应用到所有特征

如果你想对数据集中的所有特征进行非线性变换,可以使用 FunctionTransformer

from sklearn.preprocessing import FunctionTransformer

# 定义对数变换的函数
log_transformer = FunctionTransformer(np.log1p, validate=True)

# 应用对数变换到所有特征
X_train_log = log_transformer.transform(X_train)
X_test_log = log_transformer.transform(X_test)

print("Original shape:", X_train.shape)
print("Log-transformed shape:", X_train_log.shape)

这些非线性变换可以帮助捕捉特征和目标变量之间的复杂关系,从而提高模型的性能。选择适当的变换方法取决于数据的分布和模型的需求。

添加特征的平方或立方可以改进线性回归模型。其他变换通常也对变换某些特征有用,特别是应用数学函数,比如 logexpsin 。虽然基于树的模型只关注特征的顺序,但线性模型和神经网络依赖于每个特征的尺度和分布。如果在特征和目标之间存在非线性关系,那么建模就变得非常困难,特别是对于回归问题。logexp 函数可以帮助调节数据的相对比例,从而改进线性模型或神经网络的学习效果。我们之前对内存价格数据应用过这种函数。在处理具有周期性模式的数据时,sincos 函数非常有用。

大部分模型都在每个特征(在回归问题中还包括目标值)大致遵循高斯分布时表现最好,也就是说,每个特征的直方图应该具有类似于熟悉的“钟形曲线”的形状。使用诸如 logexp 之类的变换并不稀奇,但却是实现这一点的简单又有效的方法。在一种特别常见的情况下,这样的变换非常有用,就是处理整数计数数据时。计数数据是指类似“用户 A 多长时间登录一次?”这样的特征。计数不可能取负值,并且通常遵循特定的统计模式。下面我们使用一个模拟的计数数据集,其性质与在自然状态下能找到的数据集类似。特征全都是整数值,而响应是连续的:

import numpy as np

rnd = np.random.RandomState(0)
X_org = rnd.normal(size=(1000, 3))
w = rnd.normal(size=3)

X = rnd.poisson(10 * np.exp(X_org))
y = np.dot(X_org, w)

我们来看一下第一个特征的前 10 个元素。它们都是正整数,但除此之外很难找出特定的模式。

如果我们计算每个值的出现次数,那么数值的分布将变得更清楚:

print("Number of feature appearances:\n{}".format(np.bincount(X[:, 0])))
Number of feature appearances:
[28 38 68 48 61 59 45 56 37 40 35 34 36 26 23 26 27 21 23 23 18 21 10  9
 17  9  7 14 12  7  3  8  4  5  5  3  4  2  4  1  1  3  2  5  3  8  2  5
  2  1  2  3  3  2  2  3  3  0  1  2  1  0  0  3  1  0  0  0  1  3  0  1
  0  2  0  1  1  0  0  0  0  1  0  0  2  2  0  1  1  0  0  0  0  1  1  0
  0  0  0  0  0  0  1  0  0  0  0  0  1  1  0  0  1  0  0  0  0  0  0  0
  1  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1]

数字 2 似乎是最常见的,共出现了 68 次(bincount 始终从 0 开始),更大数字的出现次数快速下降。但也有一些很大的数字,比如 134 出现了 2 次 (这里 134 实际的出现次数是 0,但 84 和 85 的出现次数是 2,作者想要表达的意思是没错的) 。我们在图 4-7 中将计数可视化。

bins = np.bincount(X[:, 0])
plt.bar(range(len(bins)), bins, color='b')
plt.ylabel("Number of appearances")
plt.xlabel("Value")

plt.tight_layout()
plt.savefig('Images/04UnivariateNonlinearTransformation-01.png', bbox_inches='tight')
plt.show()
图 4-7:X[:, 0] 特征取值的直方图

特征 X[:, 1] 和 X[:, 2] 具有类似的性质。这种类型的数值分布(许多较小的值和一些非常大的值)在实践中非常常见(这是泊松分布,对计数数据相当重要)。 但大多数线性模型无法很好地处理这种数据。我们尝试拟合一个岭回归模型:

from sklearn.linear_model import Ridge
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
score = Ridge().fit(X_train, y_train).score(X_test, y_test)
print("Test score: {:.3f}".format(score))
Test score: 0.622

你可以从相对较小的 R^{2} 分数中看出,Ridge 无法真正捕捉到 X 和 y 之间的关系。不过应用对数变换可能有用。由于数据取值中包括 0(对数在 0 处没有定义),所以我们不能直接应用 log ,而是要计算 log(X + 1) :

X_train_log = np.log(X_train + 1)
X_test_log = np.log(X_test + 1)

变换之后,数据分布的不对称性变小,也不再有非常大的异常值(见图 4-8):

plt.hist(X_train_log[:, 0], bins=25, color='b')
plt.ylabel("Number of appearances")
plt.xlabel("Value")

plt.tight_layout()
plt.savefig('Images/04UnivariateNonlinearTransformation-02.png', bbox_inches='tight')
plt.show()
图 4-8:对 X[:, 0] 特征取值进行对数变换后的直方图

在新数据上构建一个岭回归模型,可以得到更好的拟合:

score = Ridge().fit(X_train_log, y_train).score(X_test_log, y_test)
print("Test score: {:.3f}".format(score))
Test score: 0.875

为数据集和模型的所有组合寻找最佳变换,这在某种程度上是一门艺术。在这个例子中,所有特征都具有相同的性质,这在实践中是非常少见的情况。通常来说,只有一部分特征应该进行变换,有时每个特征的变换方式也各不相同。前面提到过,对基于树的模型而言,这种变换并不重要,但对线性模型来说可能至关重要。对回归的目标变量 y 进行变换有时也是一个好主意。尝试预测计数(比如订单数量)是一项相当常见的任务,而且使用 log(y + 1) 变换也往往有用。(这是对泊松分布非常粗略的近似,而从概率的角度来看,这是正确的解决方法)

从前面的例子中可以看出,分箱、多项式和交互项都对模型在给定数据集上的性能有很大影响,对于复杂度较低的模型更是这样,比如线性模型和朴素贝叶斯模型。与之相反,基于树的模型通常能够自己发现重要的交互项,大多数情况下不需要显式地变换数据。其他模型,比如 SVM、最近邻和神经网络,有时可能会从使用分箱、交互项或多项式中受益,但其效果通常不如线性模型那么明显。

三、自动化特征选择

自动化特征选择是机器学习中用于提高模型性能和减少模型复杂性的重要步骤。通过自动化特征选择,我们可以选择对模型预测有显著影响的特征,去除冗余或不相关的特征,从而提高模型的泛化能力和训练效率。

有了这么多种创建新特征的方法,你可能会想要增大数据的维度,使其远大于原始特征的数量。但是,添加更多特征会使所有模型变得更加复杂,从而增大过拟合的可能性。在添加新特征或处理一般的高维数据集时,最好将特征的数量减少到只包含最有用的那些特征,并删除其余特征。这样会得到泛化能力更好、更简单的模型。但你如何判断每个特征的作用有多大呢?有三种基本的策略:单变量统计 (univariate statistics)、基于模型的选择 (model-based selection)和迭代选择 (iterative selection)。我们将详细讨论这三种策略。所有这些方法都是监督方法,即它们需要目标值来拟合模型。这也就是说,我们需要将数据划分为训练集和测试集,并只在训练集上拟合特征选择。

在单变量统计中,我们计算每个特征和目标值之间的关系是否存在统计显著性,然后选择具有最高置信度的特征。对于分类问题,这也被称为方差分析 (analysis of variance,ANOVA)。这些测试的一个关键性质就是它们是单变量的 (univariate),即它们只单独考虑每个特征。因此,如果一个特征只有在与另一个特征合并时才具有信息量,那么这个特征将被舍弃。单变量测试的计算速度通常很快,并且不需要构建模型。另一方面,它们完全独立于你可能想要在特征选择之后应用的模型。

想要在 scikit-learn 中使用单变量特征选择,你需要选择一项测试——对分类问题通常是 f_classif (默认值),对回归问题通常是 f_regression ——然后基于测试中确定的 p 值来选择一种舍弃特征的方法。所有舍弃参数的方法都使用阈值来舍弃所有 p 值过大的特征(意味着它们不可能与目标值相关)。计算阈值的方法各有不同,最简单的是 SelectKBest 和 SelectPercentile ,前者选择固定数量的 k 个特征,后者选择固定百分比的特征。我们将分类的特征选择应用于 cancer 数据集。为了使任务更难一点,我们将向数据中添加一些没有信息量的噪声特征。我们期望特征选择能能够识别没有信息量的特征并删除它们:

from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectPercentile
from sklearn.model_selection import train_test_split
import numpy as np

cancer = load_breast_cancer()

# 获得确定性的随机数
rng = np.random.RandomState(42)
noise = rng.normal(size=(len(cancer.data), 50))
# 向数据中添加噪声特征
# 前30个特征来自数据集,后50个是噪声
X_w_noise = np.hstack([cancer.data, noise])

X_train, X_test, y_train, y_test = train_test_split(
    X_w_noise, cancer.target, random_state=0, test_size=.5)
# 使用f_classif(默认值)和SelectPercentile来选择50%的特征
select = SelectPercentile(percentile=50)
select.fit(X_train, y_train)
# 对训练集进行变换
X_train_selected = select.transform(X_train)

print("X_train.shape: {}".format(X_train.shape))
print("X_train_selected.shape: {}".format(X_train_selected.shape))
X_train.shape: (284, 80)
X_train_selected.shape: (284, 40)

如你所见,特征的数量从 80 减少到 40(原始特征数量的 50%)。我们可以用 get_support 方法来查看哪些特征被选中,它会返回所选特征的布尔遮罩(mask)(其可视化见图 4-9):

import matplotlib.pyplot as plt

mask = select.get_support()
print(mask)
# 将遮罩可视化——黑色为True,白色为False
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("Sample index")

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-01.png', bbox_inches='tight')
plt.show()
[ True  True  True  True  True  True  True  True  True False  True False
  True  True  True  True  True  True False False  True  True  True  True
  True  True  True  True  True  True False False False  True False  True
 False False  True False False False False  True False False  True False
 False  True False  True False False False False False False  True False
  True False False False False  True False  True False False False False
  True  True False  True False False False False]
图 4-9:SelectPercentile 选择的特征

你可以从遮罩的可视化中看出,大多数所选择的特征都是原始特征,并且大多数噪声特征都已被删除。但原始特征的还原并不完美。我们来比较 Logistic 回归在所有特征上的性能与仅使用所选特征的性能:

from sklearn.linear_model import LogisticRegression

# 对测试数据进行变换
X_test_selected = select.transform(X_test)

lr = LogisticRegression()
lr.fit(X_train, y_train)
print("Score with all features: {:.3f}".format(lr.score(X_test, y_test)))
lr.fit(X_train_selected, y_train)
print("Score with only selected features: {:.3f}".format(
    lr.score(X_test_selected, y_test)))

在这个例子中,删除噪声特征可以提高性能,即使丢失了某些原始特征。这是一个非常简单的假想示例,在真实数据上的结果要更加复杂。不过,如果特征量太大以至于无法构建模型,或者你怀疑许多特征完全没有信息量,那么单变量特征选择还是非常有用的。

Score with all features: 0.919
Score with only selected features: 0.919

基于模型的特征选择使用一个监督机器学习模型来判断每个特征的重要性,并且仅保留最重要的特征。用于特征选择的监督模型不需要与用于最终监督建模的模型相同。特征选择模型需要为每个特征提供某种重要性度量,以便用这个度量对特征进行排序。决策树和基于决策树的模型提供了 feature_importances_ 属性,可以直接编码每个特征的重要性。线性模型系数的绝对值也可以用于表示特征重要性。正如我们在之前所见,L1 惩罚的线性模型学到的是稀疏系数,它只用到了特征的一个很小的子集。这可以被视为模型本身的一种特征选择形式,但也可以用作另一个模型选择特征的预处理步骤。与单变量选择不同,基于模型的选择同时考虑所有特征,因此可以获取交互项(如果模型能够获取它们的话)。要想使用基于模型的特征选择,我们需要使用 SelectFromModel 变换器:

from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
select = SelectFromModel(
    RandomForestClassifier(n_estimators=100, random_state=42),
    threshold="median")

SelectFromModel 类选出重要性度量(由监督模型提供)大于给定阈值的所有特征。为了得到可以与单变量特征选择进行对比的结果,我们使用中位数作为阈值,这样就可以选择一半特征。我们用包含 100 棵树的随机森林分类器来计算特征重要性。这是一个相当复杂的模型,也比单变量测试要强大得多。下面我们来实际拟合模型:

select.fit(X_train, y_train)
X_train_l1 = select.transform(X_train)
print("X_train.shape: {}".format(X_train.shape))
print("X_train_l1.shape: {}".format(X_train_l1.shape))
X_train.shape: (284, 80)
X_train_l1.shape: (284, 40)

我们可以再次查看选中的特征(见图 4-10):

mask = select.get_support()
# 将遮罩可视化——黑色为True,白色为False
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("Sample index")

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-02.png', bbox_inches='tight')
plt.show()
图 4-10:使用 RandomForestClassifier 的 SelectFromModel 选择的特征

这次,除了两个原始特征,其他原始特征都被选中。由于我们指定选择 40 个特征,所以也选择了一些噪声特征。我们来看一下其性能:

X_test_l1 = select.transform(X_test)
score = LogisticRegression().fit(X_train_l1, y_train).score(X_test_l1, y_test)
print("Test score: {:.3f}".format(score))
Test score: 0.930

利用更好的特征选择,性能也得到了提高。

在单变量测试中,我们没有使用模型,而在基于模型的选择中,我们使用了单个模型来选择特征。在迭代特征选择中,将会构建一系列模型,每个模型都使用不同数量的特征。有两种基本方法:开始时没有特征,然后逐个添加特征,直到满足某个终止条件;或者从所有特征开始,然后逐个删除特征,直到满足某个终止条件。由于构建了一系列模型,所以这些方法的计算成本要比前面讨论过的方法更高。其中一种特殊方法是递归特征消除 (recursive feature elimination,RFE),它从所有特征开始构建模型,并根据模型舍弃最不重要的特征,然后使用除被舍弃特征之外的所有特征来构建一个新模型,如此继续,直到仅剩下预设数量的特征。为了让这种方法能够运行,用于选择的模型需要提供某种确定特征重要性的方法,正如基于模型的选择所做的那样。下面我们使用之前用过的同一个随机森林模型,得到的结果如图 4-11 所示:

from sklearn.feature_selection import RFE
select = RFE(RandomForestClassifier(n_estimators=100, random_state=42),
             n_features_to_select=40)
select.fit(X_train, y_train)
# 将选中的特征可视化:
mask = select.get_support()
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("Sample index")

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-03.png', bbox_inches='tight')
plt.show()
图 4-11:使用随机森林分类器模型的递归特征消除选择的特征

与单变量选择和基于模型的选择相比,迭代特征选择的结果更好,但仍然漏掉了一个特征。运行上述代码需要的时间也比基于模型的选择长得多,因为对一个随机森林模型训练了 40 次,每运行一次删除一个特征。我们来测试一下使用 RFE 做特征选择时 Logistic 回归模型的精度:

X_train_rfe= select.transform(X_train)
X_test_rfe= select.transform(X_test)

score = LogisticRegression().fit(X_train_rfe, y_train).score(X_test_rfe, y_test)
print("Test score: {:.3f}".format(score))
Test score: 0.930

我们还可以利用在 RFE 内使用的模型来进行预测。这仅使用被选中的特征集:

print("Test score: {:.3f}".format(select.score(X_test, y_test)))
Test score: 0.951

这里,在 RFE 内部使用的随机森林的性能,与在所选特征上训练一个 Logistic 回归模型得到的性能相同。换句话说,只要我们选择了正确的特征,线性模型的表现就与随机森林一样好。

如果你不确定何时选择使用哪些特征作为机器学习算法的输入,那么自动化特征选择可能特别有用。它还有助于减少所需要的特征数量,加快预测速度,或允许可解释性更强的模型。在大多数现实情况下,使用特征选择不太可能大幅提升性能,但它仍是特征工程工具箱中一个非常有价值的工具。

四、利用专家知识

对于特定应用来说,在特征工程中通常可以利用专家知识 (expert knowledge)。虽然在许多情况下,机器学习的目的是避免创建一组专家设计的规则,但这并不意味着应该舍弃该应用或该领域的先验知识。通常来说,领域专家可以帮助找出有用的特征,其信息量比数据原始表示要大得多。想象一下,你在一家旅行社工作,想要预测机票价格。假设你有价格以及日期、航空公司、出发地和目的地的记录。机器学习模型可能从这些记录中构建一个相当不错的模型,但可能无法学到机票价格中的某些重要因素。例如,在度假高峰月份和假日期间,机票价格通常更高。虽然某些假日的日期是固定的(比如圣诞节),其影响可以从日期中学到,但其他假日的日期可能取决于月相(比如光明节和复活节),或者由官方规定(比如学校放假)。如果每个航班都只使用公历记录日期,则无法从数据中学到这些事件。但添加一个特征是很简单的,其中编码了一个航班在公休假日或学校假期的之前、之中还是之后。利用这种方法可以将关于任务属性的先验知识编码到特征中,以辅助机器学习算法。添加一个特征并不会强制机器学习算法使用它,即使最终发现假日信息不包含关于机票价格的信息,用这一信息来扩充数据也不会有什么害处。

下面我们来看一个利用专家知识的特例——虽然在这个例子中,对这些专家知识更正确的叫法应该是“常识”。任务是预测在 Andreas 家门口的自行车出租。

在纽约,Citi Bike 运营着一个带有付费系统的自行车租赁站网络。这些站点遍布整个城市,提供了一种方便的交通方式。自行车出租数据以匿名形式公开(Citi Bike System Data | Citi Bike NYC ),并用各种方法进行了分析。我们想要解决的任务是,对于给定的日期和时间,预测有多少人将会在 Andreas 的家门口租一辆自行车——这样他就知道是否还有自行车留给他。

我们首先将这个站点 2015 年 8 月的数据加载为一个 pandas 数据框。我们将数据重新采样为每 3 小时一个数据,以得到每一天的主要趋势:

import mglearn

citibike = mglearn.datasets.load_citibike()
print("Citi Bike data:\n{}".format(citibike.head()))
Citi Bike data:
starttime
2015-08-01 00:00:00     3
2015-08-01 03:00:00     0
2015-08-01 06:00:00     9
2015-08-01 09:00:00    41
2015-08-01 12:00:00    39
Freq: 3h, Name: one, dtype: int64

下面这个示例给出了整个月租车数量的可视化(图 4-12):

import pandas as pd

plt.figure(figsize=(10, 3))
xticks = pd.date_range(start=citibike.index.min(), end=citibike.index.max(),
                       freq='D')
plt.xticks(xticks, xticks.strftime("%a %m-%d"), rotation=90, ha="left")
plt.plot(citibike, linewidth=1)
plt.xlabel("Date")
plt.ylabel("Rentals")

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-04.png', bbox_inches='tight')
plt.show()
图 4-12:对于选定的 Citi Bike 站点,自行车出租数量随时间的变化

观察此数据,我们可以清楚地区分每 24 小时中的白天和夜间。工作日和周末的模式似乎也有很大不同。在对这种时间序列上的预测任务进行评估时,我们通常希望从过去学习 并预测未来 。也就是说,在划分训练集和测试集的时候,我们希望使用某个特定日期之前的所有数据作为训练集,该日期之后的所有数据作为测试集。这是我们通常使用时间序列预测的方式:已知过去所有的出租数据,我们认为明天会发生什么?我们将使用前 184 个数据点(对应前 23 天)作为训练集,剩余的 64 个数据点(对应剩余的 8 天)作为测试集。

在我们的预测任务中,我们使用的唯一特征就是某一租车数量对应的日期和时间。因此输入特征是日期和时间,比如 2015-08-01 00:00:00 ,而输出是在接下来 3 小时内的租车数量(根据我们的 DataFrame ,在这个例子中是 3)。

在计算机上存储日期的常用方式是使用 POSIX 时间(这有些令人意外),它是从 1970 年 1 月 1 日 00:00:00(也就是 Unix 时间的起点)起至现在的总秒数。首先,我们可以尝试使用这个单一整数特征作为数据表示:

# 提取目标值(租车数量)
y = citibike.values

# 将时间转换为 POSIX 时间(即时间戳)
X = citibike.index
X = pd.to_datetime(X)  # 确保索引是日期时间格式
X = X.view('int64') // 10**9  # 转换为秒时间戳并转换为整数

# 转换为二维数组
X = X.reshape(-1, 1)

print(X[:5])
print(y[:5])

我们首先定义一个函数,它可以将数据划分为训练集和测试集,构建模型并将结果可视化:

# 使用前184个数据点用于训练,剩余的数据点用于测试
n_train = 184

# 对给定特征集上的回归进行评估和作图的函数
def eval_on_features(features, target, regressor):
    # 将给定特征划分为训练集和测试集
    X_train, X_test = features[:n_train], features[n_train:]
    # 同样划分目标数组
    y_train, y_test = target[:n_train], target[n_train:]
    regressor.fit(X_train, y_train)
    print("Test-set R^2: {:.2f}".format(regressor.score(X_test, y_test)))
    y_pred = regressor.predict(X_test)
    y_pred_train = regressor.predict(X_train)
    plt.figure(figsize=(10, 3))

    plt.xticks(range(0, len(X), 8), xticks.strftime("%a %m-%d"), rotation=90,
               ha="left")

    plt.plot(range(n_train), y_train, label="train")
    plt.plot(range(n_train, len(y_test) + n_train), y_test, '-', label="test")
    plt.plot(range(n_train), y_pred_train, '--', label="prediction train")

    plt.plot(range(n_train, len(y_test) + n_train), y_pred, '--',
             label="prediction test")
    plt.legend(loc=(1.01, 0))
    plt.xlabel("Date")
    plt.ylabel("Rentals")

我们之前看到,随机森林需要很少的数据预处理,因此它似乎很适合作为第一个模型。我们使用 POSIX 时间特征 X ,并将随机森林回归传入我们的 eval_on_features 函数。结果如图 4-13 所示。

from sklearn.ensemble import RandomForestRegressor
regressor = RandomForestRegressor(n_estimators=100, random_state=0)
plt.figure()
eval_on_features(X, y, regressor)

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-05.png', bbox_inches='tight')
plt.show()
Test-set R^2: -0.04
图 4-13:随机森林仅使用 POSIX 时间做出的预测

在训练集上的预测结果相当好,这符合随机森林通常的表现。但对于测试集来说,预测结果是一条常数直线。R2 为 -0.04,说明我们什么都没有学到。发生了什么?

问题在于特征和随机森林的组合。测试集中 POSIX 时间特征的值超出了训练集中特征取值的范围:测试集中数据点的时间戳要晚于训练集中的所有数据点。树以及随机森林无法外推 (extrapolate)到训练集之外的特征范围。结果就是模型只能预测训练集中最近数据点的目标值,即最后一次观测到数据的时间。

显然,我们可以做得更好。这就是我们的“专家知识”的用武之地。通过观察训练数据中的租车数量图像,我们发现两个因素似乎非常重要:一天内的时间与一周的星期几。因此我们来添加这两个特征。我们从 POSIX 时间中学不到任何东西,所以删掉这个特征。首先,我们仅使用每天的时刻。如图 4-14 所示,现在的预测结果对一周内的每天都具有相同的模式:

# 获取小时信息并转换为二维数组
X_hour = citibike.index.hour.to_numpy().reshape(-1, 1)
eval_on_features(X_hour, y, regressor)

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-06.png', bbox_inches='tight')
plt.show()
Test-set R^2: 0.60
图 4-14:随机森林仅使用每天的时刻做出的预测

R^{2} 已经好多了,但预测结果显然没有抓住每周的模式。下面我们还添加一周的星期几作为特征(见图 4-15):

X_hour_week = np.hstack([citibike.index.dayofweek.to_numpy().reshape(-1, 1),
                         citibike.index.hour.to_numpy().reshape(-1, 1)])
eval_on_features(X_hour_week, y, regressor)

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-07.png', bbox_inches='tight')
plt.show()
Test-set R^2: 0.84
图 4-15:随机森林使用一周的星期几和每天的时刻两个特征做出的预测

现在我们的模型通过考虑一周的星期几和一天内的时间捕捉到了周期性的行为。它的 R2 为 0.84,预测性能相当好。模型学到的内容可能是 8 月前 23 天中星期几与时刻每种组合的平均租车数量。这实际上不需要像随机森林这样复杂的模型,所以我们尝试一个更简单的模型——LinearRegression (见图 4-16):

from sklearn.linear_model import LinearRegression
eval_on_features(X_hour_week, y, LinearRegression())

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-08.png', bbox_inches='tight')
plt.show()
Test-set R^2: 0.13
图 4-16:线性模型使用一周的星期几和每天的时刻两个特征做出的预测

LinearRegression 的效果差得多,而且周期性模式看起来很奇怪。其原因在于我们用整数编码一周的星期几和一天内的时间,它们被解释为连续变量。因此,线性模型只能学到关于每天时间的线性函数——它学到的是,时间越晚,租车数量越多。但实际模式比这要复杂得多。我们可以通过将整数解释为分类变量(用 OneHotEncoder 进行变换)来获取这种模式(见图 4-17):

from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import Ridge

enc = OneHotEncoder()
X_hour_week_onehot = enc.fit_transform(X_hour_week).toarray()

eval_on_features(X_hour_week_onehot, y, Ridge())

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-09.png', bbox_inches='tight')
plt.show()
Test-set R^2: 0.62
图 4-17:线性模型使用 one-hot 编码过的一周的星期几和每天的时刻两个特征做出的预测

它给出了比连续特征编码好得多的匹配。现在线性模型为一周内的每天都学到了一个系数,为一天内的每个时刻都学到了一个系数。也就是说,一周七天共享“一天内每个时刻”的模式。

利用交互特征,我们可以让模型为星期几和时刻的每一种组合学到一个系数(见图 4-18):

from sklearn.preprocessing import PolynomialFeatures

poly_transformer = PolynomialFeatures(degree=2, interaction_only=True,
                                      include_bias=False)
X_hour_week_onehot_poly = poly_transformer.fit_transform(X_hour_week_onehot)
lr = Ridge()
eval_on_features(X_hour_week_onehot_poly, y, lr)

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-10.png', bbox_inches='tight')
plt.show()
Test-set R^2: 0.85
图 4-18:线性模型使用星期几和时刻两个特征的乘积做出的预测

这一变换最终得到一个性能与随机森林类似的模型。这个模型的一大优点是,可以很清楚地看到学到的内容:对每个星期几和时刻的交互项学到了一个系数。我们可以将模型学到的系数作图,而这对于随机森林来说是不可能的。

首先,为时刻和星期几特征创建特征名称:

hour = ["%02d:00" % i for i in range(0, 24, 3)]
day = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
features =  day + hour

然后,利用 get_feature_names 方法对 PolynomialFeatures 提取的所有交互特征进行命名,并仅保留系数不为零的那些特征:

features_poly = poly_transformer.get_feature_names_out(features)
features_nonzero = np.array(features_poly)[lr.coef_ != 0]
coef_nonzero = lr.coef_[lr.coef_ != 0]

下面将线性模型学到的系数可视化,如图 4-19 所示:

plt.figure(figsize=(15, 2))
plt.plot(coef_nonzero, 'o')
plt.xticks(np.arange(len(coef_nonzero)), features_nonzero, rotation=90)
plt.xlabel("Feature name")
plt.ylabel("Feature magnitude")

plt.tight_layout()
plt.savefig('Images/05AutomatedFeatureSelection-11.png', bbox_inches='tight')
plt.show()
图 4-19:线性模型使用星期几和时刻两个特征的乘积学到的系数

附录

一、参考文献

参考文献:[德] Andreas C. Müller [美] Sarah Guido 《Python Machine Learning Basics Tutorial》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1682557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式11——代理模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用,主要是下面的UML图可以起到大作用,在你学习过一遍以后可能会遗忘,忘记了不要紧,只要看一眼UML图就能想起来了。同时也请大家多多指教。 代理模式(Proxy&am…

mfc100u.dll缺失怎么解决,详细分析mfc100u.dll文件属性

mfc100u.dll文件缺失是一个常见的问题,这通常由计算机在执行操作时不慎造成的dll文件遗失引起。作为一个关键的dll文件,如果没有mfc100u.dll,可能会导致程序无法启动或系统错误。下面,我们将讨论几种解决mfc100u.dll文件遗失的方法…

弘君资本午评:三大股指全线走低,北证50指数逆市涨逾3%

23日早盘,三大股指低开低走,盘中均跌超1%,北证50指数逆市拉升,一度涨超4%;两市半日成交近5300亿元。 到午间收盘,沪指跌1%报3126.82点,深证成指跌1.19%,创业板指跌0.98%&#xff0c…

5月23日 网络编程day6

1.IO多路复用的原理? 答:将文件描述符放入一个集合中,当集合中有事件产生,移除其他文件描述符,执行剩下的文件描述符所对应的任务,往复循环。 2.实现IO多路复用可以使用哪些函数完成? 答&…

深度学习之体育运动项目姿态估计识别计数系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在体育运动领域,对运动员的姿态进行准确估计和识别,以及对运动员数量进行精确…

苹果MacOS系统使用微软远程桌面连接Windows电脑桌面详细步骤

文章目录 前言1. 测试本地局域网内远程控制1.1 Windows打开远程桌面1.2 局域网远程控制windows 2. 测试Mac公网远程控制windows2.1 在windows电脑上安装cpolar2.2 Mac公网远程windows 3. 配置公网固定TCP地址 前言 日常工作生活中,有时候会涉及到不同设备不同操作系…

软件下载系统asp.net

本项目实现电子书下载网站的功能,实现文章、管理员分类,友情连接的管理以及对前台页面的静态化。网站前台实现对电子书的详细信息介绍和提供下载。 说明文档 运行前附加数据库.mdf(或sql生成数据库) 主要技术: 基于a…

2024 Google I/O - 提前窥探 Android 15 的新功能与适配

今年年初就简单介绍过 Android 15 预览版 的相关内容,而昨天 Google I/O 宣布了 Android 15 Beta2,作为第二个 Beta 版本 ,它已经基本接近它未来的样子,毕竟下个版本就是 Platform Stability 了,所以让我们提前来一睹 …

绿色智能:AI机器学习在环境保护中的深度应用与实践案例

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

IDEA如何对多线程进行debug

开发中使用到多线程的时候不少,但是debug起来还是比较困难的,因为默认每次只会进入一个线程,这样有些问题是发现不了的,其实IDEA也是支持进入每个线程来debug的 写一个简单的demo public class ThreadDebug {public static void main(String[] args) {MyThread myThread new…

04-Vue:ref获取页面节点--很简单

目录 前言在Vue中,通过 ref 属性获取DOM元素使用 ref 属性获取整个子组件(父组件调用子组件的方法) 前言 我们接着上一篇文章 03-02-Vue组件之间的传值 来讲。 下一篇文章 05-Vue路由 在Vue中,通过 ref 属性获取DOM元素 我们当然…

前端基础:1-2 面向对象 + Promise

面向对象 对象是什么?为什么要面向对象? 通过代码抽象,进而藐视某个种类物体的方式 特点:逻辑上迁移更加灵活、代码复用性更高、高度的模块化 对象的理解 对象是对于单个物体的简单抽象对象是容器,封装了属性 &am…

电商平台api接口:采购比价可用的比价工具推荐

电商平台api接口 目前,许多企业在进行内部采购时都有比价的需求。企业利用比价采购这一方式,能通过对比不同平台上、不同供应商的报价,进而选择最符合其需求和预算的产品或服务。 在比价采购的流程中,最重要的步骤就是企业在明确…

XXE(XML外部实体注入)

1、XXE原理 XXE(XML外部实体注入,XML External Entity) ,在应用程序解析XML输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、发起DoS拒绝服务攻击、执行系统命…

马蹄集 oj赛(双周赛第二十七次)

目录 栈的min 外卖递送 奇偶序列 sort 五彩斑斓的世界 括号家族 名次并列 栈间 双端队列 合并货物 逆序对 活动分组 栈的min 难度:黄金巴 占用内存:128 M时间限制:1秒 小码哥又被安排任务了,这次他需要要设计一个堆栈,他除了可以满足正常的栈…

英语学习笔记22——Give me/him/her/us/them a .... Which one?

Give me/him/her/us/them a … Which one? 给我/他/她/我们/他们一个…… 哪一个? 词汇 Vocabulary empty a. 空的,啥也没有的    v. 倒空 例句:这个盒子是空的。    This box is empty.    这是个空盒子。    This is an emp…

顶顶通呼叫中心中间件-自动外呼输入分机号(比如隐私号)(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-自动外呼输入分机号(比如隐私号)(mod_cti基于FreeSWITCH) 比如有些人的号码是这样的就需要用上自动外呼输入分机号了 号码1:182XXXX8111-1234 号码2:182XXXX8222 如果号码是这样的就根据以下步骤配置 注意使用这个需要:…

深度学习模型keras第二十三讲:在KerasCV中使用SAM进行任何图像分割

1 SAM概念 ###1.1 SAM定义 Segment Anything Model(SAM)是一种基于深度学习的图像分割模型,其主要特点包括: 高质量的图像分割:SAM可以从输入提示(如点、框、文字等)生成高质量的对象掩模&am…

自动化测试在软件开发生命周期中如何提高代码质量?

自动化测试是一种在软件开发生命周期中使用软件工具来执行测试的方法,它可以大大提高代码质量,减少开发过程中的错误和缺陷。本文将从零开始,详细且规范地介绍如何使用自动化测试来提高代码质量。 第一步:明确测试目标 在开始自…

JMH301【亲测】5月最新整理【神鬼传奇】斗罗超变单机版175级新宠物宝宝坐骑丰富超变定制装备带完整GM命令网游单机虚拟机一键端

资源介绍: 是否需要虚拟机:是 文件大小:压缩包约8.6G 支持系统:win7、win10、win11 硬件需求:运行内存8G 4核及以上CPU 下载方式:百度网盘 内容持续更新! 资源截图: 下载地址…