数据馈送机器学习模型,越多越好,对吗?好吧,有时数字数据不太适合提取,因此,本文将介绍多种方法,可以将原始数字转换为更可口的东西。
数值数据几乎是福气。为什么差不多?好吧,因为它已经采用了机器学习模型可以摄取的格式。但是,如果我们将其翻译成与人类相关的术语,仅仅因为博士级别的教科书是用英语写的(我会说,读和写英语),并不意味着我有足够的理解能力来获得有用的见解。使教科书对我有用的是,如果它以一种考虑我的心理模型假设的方式来概括最重要的信息,例如“数学是神话”(顺便说一句,自从我以后就不再是我的观点了)真的开始享受它)。同样,良好的功能应代表数据的显着方面,并采用由机器学习模型做出的假设的形式。
特征工程是从原始数据中提取特征并将其转换为可由机器学习模型摄取的格式的过程。通常需要进行转换以减轻建模的难度并提高模型的结果。因此,设计数值数据类型的技术是数据科学家(无论是机器学习工程师)还是其炮兵的基本工具。
“数据就像 机器学习的原油一样 ,这意味着必须将其精炼为 特征 (预测变量)才能对训练模型有用。” — 威尔·科森
在我们努力精通掌握的过程中,重要的是要指出,仅仅知道一种机制为什么起作用以及它可以做什么还远远不够。Mastery知道如何做某事,对基本原理有直觉,并具有神经连接,使得面对挑战时,绘制正确的工具成为无缝的过程。这不是从阅读本文中得出的,而是从本文的有意实践中提供的技术直觉为您打开了大门,使您可以理解这些技术的使用方式和时间。
数据中的功能将直接影响您使用的预测模型和可获得的结果。” — 杰森·布朗利( Jason Brownlee)
有时可能会在累积的要素上收集数据,从而具有无限的上限。这种连续数据的示例可能是跟踪系统,该系统监视我的所有博客帖子每天收到的访问次数。这种类型的数据很容易吸引异常值,因为可能会有一些不可预测的事件影响到我的文章所累积的总流量。当可以快速,大量地收集数据时,很可能其中包含一些需要工程设计的极端值。
一些处理此实例的方法是:
量化
此方法通过将值分组到bin中来包含数据的规模。因此,量化将连续值映射为离散值,并且从概念上讲,这可以认为是有序的bin序列。要实现这一点,我们必须考虑我们创建的垃圾箱的宽度,其解决方案分为两类:固定宽度垃圾箱或自适应垃圾箱。
注意:这对于线性模型特别有用。在基于树的模型中,这是没有用的,因为基于树的模型会自行进行拆分。
在固定宽度方案中,该值是自动或自定义设计的,可将数据分割为离散的bin-它们也可以线性缩放或指数缩放。一个流行的示例是按十年间隔将人们的年龄划分为多个分区,以使bin 1包含0–9岁,bin 2具有10–19岁,依此类推。
请注意,如果值跨较大的数值范围,则更好的方法可能是将值分组为常数的幂,例如10:0–9、10–99、100–999、1000的幂–9999。请注意,bin宽度呈指数增长,因此,在1000–9999的情况下,bin宽度为O(10000),而0–9为O(10)。取计数的日志以从计数映射到数据的bin。
代码语言:javascript复制import numpy as np
#15 random integers from the "discrete uniform" distribution
ages = np.random.randint(0, 100, 15)
#evenly spaced binsages_binned = np.floor_divide(ages, 10)
print(f"Ages: {ages} nAges Binned: {ages_binned} n")
>>> Ages: [97 56 43 73 89 68 67 15 18 36 4 97 72 20 35]
Ages Binned: [9 5 4 7 8 6 6 1 1 3 0 9 7 2 3]
#numbers spanning several magnitudesviews = [300, 5936, 2, 350, 10000, 743, 2854, 9113, 25, 20000, 160, 683, 7245, 224]
#map count -> exponential width binsviews_exponential_bins = np.floor(np.log10(views))
print(f"Views: {views} nViews Binned: {views_exponential_bins}")
>>> Views: [300, 5936, 2, 350, 10000, 743, 2854, 9113, 25, 20000, 160, 683, 7245, 224]
Views Binned: [2. 3. 0. 2. 4. 2. 3. 3. 1. 4. 2. 2. 3. 2.]
当计数中的间隙较大时,自适应垃圾箱更适合。当计数值之间有较大的边距时,某些固定宽度的纸槽将为空。
要进行自适应装仓,我们可以利用数据的分位数-将数据划分为相等部分(例如中位数)的值。
代码语言:javascript复制import pandas as pd
#map the counts to quantiles (adaptive binning)
views_adaptive_bin = pd.qcut(views, 5, labels=False)
print(f"Adaptive bins: {views_adaptive_bin}")
>>> Adaptive bins: [1 3 0 1 4 2 3 4 0 4 0 2 3 1]
动力转换
我们已经看到了这样一个例子:对数变换是方差稳定变换族(称为幂变换)的一部分。Wikipedia将幂变换描述为 “用于稳定方差,使数据更像正态分布,提高关联度量(例如变量之间的Pearson相关性以及其他数据稳定程序)的有效性的技术”。
为什么我们要转换数据以适合正态分布?好问题!您可能要使用参数模型(一种对数据进行假设的模型)而不是非参数模型。当数据呈正态分布时,参数模型将很强大。但是,在某些情况下,我们需要的数据可能需要帮助才能显示出正态分布的漂亮钟形曲线。例如,数据可能会偏斜,因此我们应用幂变换来帮助使我们的特征看起来更呈高斯分布。
下面的代码利用了诸如pandas,scipy和numpy之类的数据科学框架来演示幂转换,并使用Plotly.py框架对交互式绘图进行可视化。所使用的数据集是 Kaggle 的 房屋价格:高级回归技术,您可以轻松下载(单击此处以访问数据:https://www.kaggle.com/c/house-prices-advanced-regression-techniques/data)。
代码语言:javascript复制import numpy as np
import pandas as pdfrom scipy import stats
import plotly.graph_objects as gofrom plotly.subplots import make_subplots
df = pd.read_csv("../data/raw/train.csv")
# applying various transformationsx_log = np.log(df["GrLivArea"].copy()) # log
x_square_root = np.sqrt(df["GrLivArea"].copy()) # square root x_boxcox, _ = stats.boxcox(df["GrLivArea"].copy()) # boxcox
x = df["GrLivArea"].copy() # original data
# creating the figuresfig = make_subplots(rows=2, cols=2,
horizontal_spacing=0.125,
vertical_spacing=0.125,
subplot_titles=("Original Data",
"Log Transformation",
"Square root transformation",
"Boxcox Transformation")
)# drawing the plotsfig.add_traces([ go.Histogram(x=x,
hoverinfo="x",
showlegend=False), go.Histogram(x=x_log,
hoverinfo="x",
showlegend=False), go.Histogram(x=x_square_root,
hoverinfo="x",
showlegend=False), go.Histogram(x=x_boxcox,
hoverinfo="x",
showlegend=False), ], rows=[1, 1, 2, 2],
cols=[1, 2, 1, 2]
)fig.update_layout( title=dict( text="GrLivArea with various Power Transforms",
font=dict( family="Arial",
size=20)),
showlegend=False, width=800,
height=500)
fig.show() # display figure
注意:Box-cox转换仅在数据为非负数时起作用
“其中哪一个最好?您无法事先知道。您必须尝试它们,并评估结果以实现算法和性能指标。” — 杰森·布朗利
功能缩放
顾名思义,要素缩放(也称为要素归一化)与更改要素的缩放比例有关。当数据集的特征在比例上差异很大时,对输入特征的比例敏感的模型(即线性回归,逻辑回归,神经网络)将受到影响。确保功能在相似的范围内势在必行。而基于树的模型(即决策树,随机森林,梯度增强)之类的模型并不关心规模。
缩放特征的常用方法包括最小-最大缩放,标准化和L²标准化。以下是python的简要介绍和实现。
最小-最大缩放 -将特征缩放到固定范围(通常在0–1之间),这意味着我们将减少标准偏差,因此可以抑制离群值对特征的影响。其中x是实例的个体值(特征1,特征2),max(x),min(x)是特征的最大值和最小值—请参见下图。有关此的更多信息,请参见 sklearn文档。
最小-最大缩放比例的公式
标准化 -将对特征值进行重新缩放,以使其适合均值为0且标准偏差为1的正态分布的属性。为此,我们从所有实例中减去特征的均值-在所有实例中功能实例值,然后除以方差—参见下图。有关标准化,请参阅sklearn文档。
标准化公式
L2归一化 -此技术将原始特征值除以L2范数(也是欧几里德距离)下图中的第二个等式。L²范数取所有实例中特征集中值的平方和。请参考sklearn 文档 中的L2范数(请注意,也可以通过将norm参数设置为“ L1”来进行L1归一化)。
代码语言:javascript复制可视化特征缩放的效果将更好地显示正在发生的事情。为此,我使用了可以从sklearn数据集导入的wine数据集。
import pandas as pd
from sklearn.datasets import load_winefrom sklearn.preprocessing import StandardScaler, MinMaxScaler, Normalizerimport plotly.graph_objects as gowine_json= load_wine() # load in dataset
df = pd.DataFrame(data=wine_json["data"], columns=wine_json["feature_names"]) # create pandas dataframe
df["Target"] = wine_json["target"] # created new column and added target labels
# standardizationstd_scaler = StandardScaler().fit(df[["alcohol", "malic_acid"]])
df_std = std_scaler.transform(df[["alcohol", "malic_acid"]])
# minmax scalingminmax_scaler = MinMaxScaler().fit(df[["alcohol", "malic_acid"]])
df_minmax = minmax_scaler.transform(df[["alcohol", "malic_acid"]])
# l2 normalizationl2norm = Normalizer().fit(df[["alcohol", "malic_acid"]])
df_l2norm = l2norm.transform(df[["alcohol", "malic_acid"]])
# creating tracestrace1 = go.Scatter(x= df_std[:, 0],
y= df_std[:, 1],
mode= "markers",
name= "Standardized Scale")
trace2 = go.Scatter(x= df_minmax[:, 0],
y= df_minmax[:, 1],
mode= "markers",
name= "MinMax Scale")
trace3 = go.Scatter(x= df_l2norm[:, 0],
y= df_l2norm[:, 1],
mode= "markers",
name= "L2 Norm Scale")
trace4 = go.Scatter(x= df["alcohol"],
y= df["malic_acid"],
mode= "markers",
name= "Original Scale")
layout = go.Layout( title= "Effects of Feature scaling",
xaxis=dict(title= "Alcohol"),
yaxis=dict(title= "Malic Acid")
)data = [trace1, trace2, trace3, trace4]fig = go.Figure(data=data, layout=layout)fig.show()
原始功能和各种缩放实现的图
功能互动
我们可以使用要素之间成对交互的乘积来创建逻辑AND函数。在基于树的模型中,这些交互是隐式发生的,但是在假定要素相互独立的模型中,我们可以显式声明要素之间的交互,以改善模型的输出。
考虑一个简单的线性模型,该模型使用输入要素的线性组合来预测输出y:
线性模型的公式
我们可以扩展线性模型以捕获要素之间发生的相互作用。
扩展线性模型
注意:线性函数使用起来很昂贵,并且具有成对交互作用的线性模型的评分和训练会从O(n)到O(n²)。但是,您可以执行特征提取来克服此问题(特征提取不在本文讨论范围之内,但是我将在以后的文章中进行讨论)。
让我们用python编写代码,我将利用scitkit-learn PolynomialFeatures 类,您可以在文档中了解更多信息
代码语言:javascript复制import numpy as np
from sklearn.preprocessing import PolynomialFeatures
# creating dummy dataset
X = np.arange(10).reshape(5, 2)
X.shape
>>> (5, 2)
# interactions between features only
interactions = PolynomialFeatures(interaction_only=True)
X_interactions= interactions.fit_transform(X)
X_interactions.shape
>>> (5, 4)
# polynomial features
polynomial = PolynomialFeatures(5)
X_poly = polynomial.fit_transform(X)
X_poly.shape
>>> (5, 6)
这篇文章的灵感来自《面向机器学习的特征工程:面向数据科学家的原理和技术》一书 ,我绝对建议阅读。尽管它于2016年发布,但即使对于没有数学背景的人,它仍然非常有用,而且解释清楚。
结论
在本文中,我们讨论了用于处理数字特征的技术,例如量化,幂转换,特征缩放和交互特征(可应用于各种数据类型)。这绝不是功能工程的千篇一律,而且每天都有很多东西要学习。特征工程是一门艺术,需要实践,因此,有了直觉,您就可以开始练习了。