数据表示与特征工程

我们一直假设数据是由浮点数组成的二维数组,其中每一列是描述数据点的连续特征(continuous feature)。一种特别常见的特征类型就是分类特征(categorical feature),也叫离散特征(discrete feature)。这种特征通常并不是数值。分类特征与连续特征之间的区别类似于分类和回归之间的区别,只是前者在输入端而不是输出端。我们已经见过的连续特征的例子包括像素明暗程度和花的尺寸测量。分类特征的例子包括产品的品牌、产品的颜色或产品的销售部门(图
书、服装、硬件)。这些都是描述一件产品的属性,但它们不以连续的方式变化。
对于某个特定应用来说,如何找到最佳数据表示,这个问题被称为特征工程(feature engineering),它是数据科学家和机器学习从业者在尝试解决现实世界问题时的主要任务之一。用正确的方式表示数据,对监督模型性能的影响比所选择的精确参数还要大。

分类变量

使用美国成年人收入的数据集,adult 数据集的任务是预测一名工人的收入是高于 50 000 美元还是低于 50 000 美元。这个数据集的特征包括工人的年龄、雇用方式(独立经营、私营企业员工、政府职员等)、教育水平、性别、每周工作时长、职业,等等。

在这个数据集中,age(年龄)和 hours-per-week(每周工作时长)是连续特征,我们知道如何处理这种特征。但 workclass(工作类型)、education(教育程度)、gender(性别)、occupation(职业)都是分类特征。它们都来自一系列固定的可能取值(而不是一个范围),表示的是定性属性(而不是数量)。

其中 w[i] 和 b 是从训练集中学到的系数,x[i] 是输入特征。当 x[i] 是数字时这个公式才有意义,但如果 x[2] 是 “Masters” 或 “Bachelors” 的话,这个公式则没有意义。显然,在应用 Logistic 回归时,我们需要换一种方式来表示数据。下一节将会说明我们如何解决这一问题。

one-hot编码

表示分类变量最常用的方法就是使用 one-hot 编码(one-hot-encoding)或N 取一编码(one-out-of-N encoding),也叫虚拟变量(dummy variable)。虚拟变量背后的思想是将一个分类变量替换为一个或多个新特征,新特征取值为 0 和 1。

将数据转换为分类变量的 one-hot 编码有两种方法:一种是使用 pandas,一种是使用scikit-learn。

# 使用pandas编码one-hot
# 使用pandas从逗号分割值(csv)文件中加载数据import pandas as pd
from IPython.display import display# 文件中没有包含列名称的表头,header = None
# 在names中显示的提供列名称
data = pd.read_csv("data/adult.data",header=None,index_col=False,names=['age', 'workclass', 'fnlwgt', 'education', 'education-num','marital-status', 'occupation', 'relationship', 'race', 'gender','capital-gain', 'capital-loss', 'hours-per-week', 'native-country','income'])
# 只选取其中的几列进行展示
data = data[['age', 'workclass', 'education', 'gender', 'hours-per-week','occupation', 'income']]
display(data.head())允许结果:age          workclass  ...          occupation  income
0   39          State-gov  ...        Adm-clerical   <=50K
1   50   Self-emp-not-inc  ...     Exec-managerial   <=50K
2   38            Private  ...   Handlers-cleaners   <=50K
3   53            Private  ...   Handlers-cleaners   <=50K
4   28            Private  ...      Prof-specialty   <=50K

检查字符串编码的分类数据

读取完这样的数据集之后,最好先检查每一列是否包含有意义的分类数据。检查列的内容有一个好方法,就是使用 pandas Serie(Series 是 DataFrame 中单列对应的数据类型)的 value_counts 函数,以显示唯一值及其出现次数。

print(data.gender.value_counts())运行结果:Male      21790Female    10771
Name: gender, dtype: int64

在这个数据集中性别刚好有两个值:Male 和 Female,这说明数据格式已经很好,可以用 one-hot 编码来表示。

print("Original features:\n", list(data.columns), "\n")
data_dummies = pd.get_dummies(data)
print("Features afterget_dummies:\n",list(data_dummies.columns))运行结果:
Original features:['age', 'workclass', 'education', 'gender', 'hours-per-week', 'occupation', 'income'] Features after get_dummies:['age', 'hours-per-week', 'workclass_ ?', 'workclass_ Federal-gov', 'workclass_ Local-gov', 'workclass_ Never-worked', 'workclass_ Private', 'workclass_ Self-emp-inc', 'workclass_ Self-emp-not-inc', 'workclass_ State-gov', 'workclass_ Without-pay', 'education_ 10th', 'education_ 11th', 'education_ 12th', 'education_ 1st-4th', 'education_ 5th-6th', 'education_ 7th-8th', 'education_ 9th', 'education_ Assoc-acdm', 'education_ Assoc-voc', 'education_ Bachelors', 'education_ Doctorate', 'education_ HS-grad', 'education_ Masters', 'education_ Preschool', 'education_ Prof-school', 'education_ Some-college', 'gender_ Female', 'gender_ Male', 'occupation_ ?', 'occupation_ Adm-clerical', 'occupation_ Armed-Forces', 'occupation_ Craft-repair', 'occupation_ Exec-managerial', 'occupation_ Farming-fishing', 'occupation_ Handlers-cleaners', 'occupation_ Machine-op-inspct', 'occupation_ Other-service', 'occupation_ Priv-house-serv', 'occupation_ Prof-specialty', 'occupation_ Protective-serv', 'occupation_ Sales', 'occupation_ Tech-support', 'occupation_ Transport-moving', 'income_ <=50K', 'income_ >50K']display(data_dummies.head())
运行结果:
Features after get_dummies:['age', 'hours-per-week', 'workclass_ ?', 'workclass_ Federal-gov', 'workclass_ Local-gov', 'workclass_ Never-worked', 'workclass_ Private', 'workclass_ Self-emp-inc', 'workclass_ Self-emp-not-inc', 'workclass_ State-gov', 'workclass_ Without-pay', 'education_ 10th', 'education_ 11th', 'education_ 12th', 'education_ 1st-4th', 'education_ 5th-6th', 'education_ 7th-8th', 'education_ 9th', 'education_ Assoc-acdm', 'education_ Assoc-voc', 'education_ Bachelors', 'education_ Doctorate', 'education_ HS-grad', 'education_ Masters', 'education_ Preschool', 'education_ Prof-school', 'education_ Some-college', 'gender_ Female', 'gender_ Male', 'occupation_ ?', 'occupation_ Adm-clerical', 'occupation_ Armed-Forces', 'occupation_ Craft-repair', 'occupation_ Exec-managerial', 'occupation_ Farming-fishing', 'occupation_ Handlers-cleaners', 'occupation_ Machine-op-inspct', 'occupation_ Other-service', 'occupation_ Priv-house-serv', 'occupation_ Prof-specialty', 'occupation_ Protective-serv', 'occupation_ Sales', 'occupation_ Tech-support', 'occupation_ Transport-moving', 'income_ <=50K', 'income_ >50K']age  hours-per-week  ...  income_ <=50K  income_ >50K
0   39              40  ...              1             0
1   50              13  ...              1             0
2   38              40  ...              1             0
3   53              40  ...              1             0
4   28              40  ...              1             0

在训练模型之前,注意要把目标变量(现在被编码为两个 income 列)从数据中分离出来。将输出变量或输出变量的一些导出属性包含在特征表示中,这是构建监督机器学习模型时一个非常常见的错误。

# values 属性将 data_dummies 数据框(DataFrame)转换为 NumPy 数组
# 仅提取包含特征的列
features = data_dummies.loc[:, 'age':'occupation_ Transport-moving']
# 提取NumPy数组
X = features.values
y = data_dummies['income_ >50K'].values
print("X.shape: {} y.shape: {}".format(X.shape, y.shape))# 此时数据已经处理完全,可以用作模型的训练
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
print("Test score: {:.2f}".format(logreg.score(X_test, y_test)))
运行结果:
Test score: 0.81

数字可以编码分类变量

在上面的例子中,分类变量被编码为字符串。无论是为了便于存储还是因为数据的收集方式,分类变量通常被编码为整数。分类特征通常用整数进行编码,它们是数字并不以为着它们必须被视为连续的特征。一个整数特征应该被视为连续的还是离散的,有时不明确。如果在被编码的语义之间没有顺序关系,那么特征必须被视为离散特征。对于其他情况,哪种编码更好取决于具体的任务和数据,以及使用哪种机器学习算法。
pandas 的 get_dummies 函数将所有数字看作是连续的,不会为其创建虚拟变量。

# 创建一个DataFrame,包含一个整数特征和一个分类字符串特征
demo_df = pd.DataFrame({'Integer Feature': [0, 1, 2, 1],'Categorical Feature': ['socks', 'fox', 'socks', 'box']})
display(demo_df)


如果你想为“Integer Feature”这一列创建虚拟变量,可以使用 columns 参数显式地给出想要编码的列。

demo_df['Integer Feature'] = demo_df['Integer Feature'].astype(str)
display(pd.get_dummies(demo_df, columns=['Integer Feature', 'Categorical Feature']))

分箱、离散化、线性模型与树

数据表示的最佳方法不仅取决于数据的语义,还取决于所使用的模型种类。线性模型与基于树的模型(比如决策树、梯度提升树和随机森林)是两种成员很多同时又非常常用的模型,它们在处理不同的特征表示时就具有非常不同的性质。

from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
X, y = mglearn.datasets.make_wave(n_samples=100)
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
reg = DecisionTreeRegressor(min_samples_split=3).fit(X, y)
plt.plot(line, reg.predict(line), label="decision tree")
reg = LinearRegression().fit(X, y)
plt.plot(line, reg.predict(line), label="linear regression")
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("Regression output")
plt.xlabel("Input feature")
plt.legend(loc="best")
plt.show()


线性模型只能对线性关系建模,对于单个特征的情况就是直线。决策树可以构建更为复杂的数据模型,但这强烈依赖于数据表示。有一种方法可以让线性模型在连续数据上变得更加强大,就是使用特征分箱(binning,也叫离散化,即 discretization)将其划分为多个特征。
我们假设将特征的输入范围(-3~3)划分成固定个数的箱子,那么数据点就可以用它所在的箱子进行表示。

# 创建箱子
# np.linspace 函数创建 11 个元素,从而创建 10 个箱子,即两个连续边界之间的空间
bins = np.linspace(-3, 3, 11)
print("bins: {}".format(bins))运行空间:
bins: [-3.  -2.4 -1.8 -1.2 -0.6  0.   0.6  1.2  1.8  2.4  3. ]

这里第一个箱子包含特征取值在 -3 到 -2.4 之间的所有数据点,第二个箱子包含特征取值在 -2.4 到 -1.8 之间的所有数据点,以此类推。

which_bin = np.digitize(X, bins=bins)
print("\nData points:\n", X[:5])
print("\nBin membership for data points:\n", which_bin[:5])运行结果:
Data points:[[-0.75275929][ 2.70428584][ 1.39196365][ 0.59195091][-2.06388816]]Bin membership for data points:[[ 4][10][ 8][ 6][ 2]]

将wave数据集中单个连续输入特征变换为一个分类特征,用于表示数据所在的箱子。
要想在这个数据上使用 scikit-learn 模型,我们利用 preprocessing 模
块的 OneHotEncoder 将这个离散特征变换为 one-hot 编码OneHotEncoder 实现的编码与pandas.get_dummies 相同,但目前它只适用于值为整数的分类变量。

from sklearn.preprocessing import OneHotEncoder
# 使用OneHotEncoder进行变换
encoder = OneHotEncoder(sparse=False)
# encoder.fit找到which_bin中的唯一值
encoder.fit(which_bin)
# transform创建one-hot编码
X_binned = encoder.transform(which_bin)
print(X_binned[:5])运行结果:
# 指定10个箱子所以数据集具有10个特征
[[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.][0. 0. 0. 0. 0. 0. 0. 0. 0. 1.][0. 0. 0. 0. 0. 0. 0. 1. 0. 0.][0. 0. 0. 0. 0. 1. 0. 0. 0. 0.][0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]]
#在 one-hot 编码后的数据上构建新的线性模型和新的决策树模型
line_binned = encoder.transform(np.digitize(line, bins=bins))reg = LinearRegression().fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='linear regression binned')
reg = DecisionTreeRegressor(min_samples_split=3).fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='decision tree binned')plt.plot(X[:, 0], y, 'o', c='k')
plt.vlines(bins, -3, 3, linewidth=1, alpha=.2)
plt.legend(loc="best")
plt.ylabel("Regression output")
plt.xlabel("Input feature")
plt.show()


在分箱特征上比较线性回归和决策树回归,虚线和线完全重合,说明线性回归模型和决策树做出相同的预测,对于每个箱子,二者都会预测一个常数值。因为每个箱子内的特征是不变的,所以对于一个箱子内所有的点,任何模型都会预测相同的值。比较对特征进行分箱前后模型学到的内容,线性模型更加灵活了,因为她对每个箱子都具有不同的取值,而决策树模型的灵活性降低了。分箱特征对基于树的模型不会产生更好的效果。因为这种模型可以学习在任何位置划分数据。从某种意义上讲,决策树可以学习如何分箱对预测这些数据最为有用。决策树可以同时查看多个特征,分箱通常针对的是单个特征。

交互特征与多项式特征

想要丰富特征表示,特别是对于线性模型而言,另一种方法是添加原始数据的交互特征(interaction feature)和多项式特征(polynomial feature)。这种特征工程通常用于统计建模,但也常用于许多实际的机器学习应用中。

线性模型对wave数据集中的每个箱子都学到一个常数值。但是线性模型不仅可以学习偏移还可以学习斜率。给分箱数据上的线性模型添加斜率,一种方法是重新加入原始特征。

# 加入X轴 得到11维度的数据
# np.hstack()在水平方向上平铺
X_combined = np.hstack([X, X_binned])
print(X_combined.shape)
reg = LinearRegression().fit(X_combined, y)
line_combined = np.hstack([line, line_binned])
plt.plot(line, reg.predict(line_combined), label='linear regression combined')
for bin in bins:plt.plot([bin, bin], [-3, 3], ':', c='k')
plt.legend(loc="best")
plt.ylabel("Regression output")
plt.xlabel("Input feature")
plt.plot(X[:, 0], y, 'o', c='k')
plt.show()运行结果:
(100, 11)


模型在每个箱子中都学到一个偏移,还学到一个斜率。学到的斜率是向下的,并且在所有箱子中都相同——只有一个 x 轴特征,也就只有一个斜率。因为斜率在所有箱子中是相同的,所以它似乎不是很有用。我们更希望每个箱子都有一个不同的斜率!

X_product = np.hstack([X_binned, X * X_binned])
print(X_product.shape)运行结果:
(100, 20)

这个数据集现在有20个特征:数据点所在箱子的指示符与原始特征和箱子指示符的乘积。将乘积特征看作每个箱子x轴特征的单独副本。他在箱子内等于原始特征,在其他位置等于0.

# 添加交互特征或乘积特征,用来表示数据点所在的箱子以及数据点在x轴的位置
# 这个特征是箱子指示符与原始特征的乘积
X_product = np.hstack([X_binned, X * X_binned])
print(X_product.shape)reg = LinearRegression().fit(X_product, y)
line_product = np.hstack([line_binned, line * line_binned])
plt.plot(line, reg.predict(line_product), label='linear regression product')
for bin in bins:plt.plot([bin, bin], [-3, 3], ':', c='k')
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("Regression output")
plt.xlabel("Input feature")
plt.legend(loc="best")
plt.show()


现在这个模型中每个箱子都有自己的斜率和偏移。

# 使用原始特征的多项式
from sklearn.preprocessing import PolynomialFeatures
# 包含直到x ** 10的多项式:
# 默认的"include_bias=True"添加恒等于1的常数特征
poly = PolynomialFeatures(degree=10, include_bias=False)
poly.fit(X)
X_poly = poly.transform(X)
print("X_poly.shape: {}".format(X_poly.shape))# 比较 X_poly 和 X 的元素
print("Entries of X:\n{}".format(X[:5]))
print("Entries of X_poly:\n{}".format(X_poly[:5]))# 通过调用 get_feature_names 方法来获取特征的语义,给出每个特征的指数
print("Polynomial feature names:\n{}".format(poly.get_feature_names()))
# 将多项式特征与线性回归模型一起使用,可以得到经典的多项式回归
reg = LinearRegression().fit(X_poly, y)
line_poly = poly.transform(line)
plt.plot(line, reg.predict(line_poly), label='polynomial linear regression')
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("Regression output")
plt.xlabel("Input feature")
plt.legend(loc="best")
plt.show()运行结果:
X_poly.shape: (100, 10)
Entries of X:
[[-0.75275929][ 2.70428584][ 1.39196365][ 0.59195091][-2.06388816]]
Entries of X_poly:
[[-7.52759287e-01  5.66646544e-01 -4.26548448e-01  3.21088306e-01-2.41702204e-01  1.81943579e-01 -1.36959719e-01  1.03097700e-01-7.76077513e-02  5.84199555e-02][ 2.70428584e+00  7.31316190e+00  1.97768801e+01  5.34823369e+011.44631526e+02  3.91124988e+02  1.05771377e+03  2.86036036e+037.73523202e+03  2.09182784e+04][ 1.39196365e+00  1.93756281e+00  2.69701700e+00  3.75414962e+005.22563982e+00  7.27390068e+00  1.01250053e+01  1.40936394e+011.96178338e+01  2.73073115e+01][ 5.91950905e-01  3.50405874e-01  2.07423074e-01  1.22784277e-017.26822637e-02  4.30243318e-02  2.54682921e-02  1.50759786e-028.92423917e-03  5.28271146e-03][-2.06388816e+00  4.25963433e+00 -8.79140884e+00  1.81444846e+01-3.74481869e+01  7.72888694e+01 -1.59515582e+02  3.29222321e+02-6.79478050e+02  1.40236670e+03]]
Polynomial feature names:
['x0', 'x0^2', 'x0^3', 'x0^4', 'x0^5', 'x0^6', 'x0^7', 'x0^8', 'x0^9', 'x0^10']


多项式特征在这个一维数据上得到了非常平滑的拟合。但高次多项式在边界上或数据很少的区域可能有极端的表现。

使用更加复杂的SVM模型与多形式回归预测的结果类似,但是多项式回归不需要进行显示的格式转换。

# 波士顿房价 对交互特征和多项式特征的应用
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScalerboston = load_boston()
X_train, X_test, y_train, y_test = train_test_split(boston.data, boston.target, random_state=0)# 缩放数据 将数据缩放到0和1之间
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)# 提取多形式特征和交互特征 次数最高为2
poly = PolynomialFeatures(degree=2).fit(X_train_scaled)X_train_poly = poly.transform(X_train_scaled)
X_test_poly = poly.transform(X_test_scaled)print("X_train.shape: {}".format(X_train.shape))
print("X_train_poly.shape: {}".format(X_train_poly.shape))运行结果
X_train.shape: (379, 13)
X_train_poly.shape: (379, 105)

原始数据有 13 个特征,现在被扩展到 105 个交互特征。这些新特征表示两个不同的原始特征之间所有可能的交互项,以及每个原始特征的平方。这里 degree=2 的意思是,我们需要由最多两个原始特征的乘积组成的所有特征。利用 get_feature_names 方法可以得到输
入特征和输出特征之间的确切对应关系:

print("Polynomial feature names:\n{}".format(poly.get_feature_names()))运行结果:
Polynomial feature names:
['1', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10','x11', 'x12', 'x0^2', 'x0 x1', 'x0 x2', 'x0 x3', 'x0 x4', 'x0 x5', 'x0 x6','x0 x7', 'x0 x8', 'x0 x9', 'x0 x10', 'x0 x11', 'x0 x12', 'x1^2', 'x1 x2','x1 x3', 'x1 x4', 'x1 x5', 'x1 x6', 'x1 x7', 'x1 x8', 'x1 x9', 'x1 x10','x1 x11', 'x1 x12', 'x2^2', 'x2 x3', 'x2 x4', 'x2 x5', 'x2 x6', 'x2 x7','x2 x8', 'x2 x9', 'x2 x10', 'x2 x11', 'x2 x12', 'x3^2', 'x3 x4', 'x3 x5','x3 x6', 'x3 x7', 'x3 x8', 'x3 x9', 'x3 x10', 'x3 x11', 'x3 x12', 'x4^2','x4 x5', 'x4 x6', 'x4 x7', 'x4 x8', 'x4 x9', 'x4 x10', 'x4 x11', 'x4 x12','x5^2', 'x5 x6', 'x5 x7', 'x5 x8', 'x5 x9', 'x5 x10', 'x5 x11', 'x5 x12','x6^2', 'x6 x7', 'x6 x8', 'x6 x9', 'x6 x10', 'x6 x11', 'x6 x12', 'x7^2','x7 x8', 'x7 x9', 'x7 x10', 'x7 x11', 'x7 x12', 'x8^2', 'x8 x9', 'x8 x10','x8 x11', 'x8 x12', 'x9^2', 'x9 x10', 'x9 x11', 'x9 x12', 'x10^2', 'x10 x11','x10 x12', 'x11^2', 'x11 x12', 'x12^2']

对 Ridge 在有交互特征的数据上和没有交互特征的数据上的性能进行对比,在使用 Ridge 时,交互特征和多项式特征对性能有很大的提升。
如果使用更加复杂的模型(比如随机森林),即使没有额外的特征,随机森林的性能也要优于 Ridge。添加交互特征和多项式特征实际上会略微降低其性能。

单变量非线性变换

添加特征的平方或立方可以改进线性回归模型,其他变换通常也对变换某些特征有用,特别是应用数学函数,比如log、exp和sin.如果特征和目标之间存在非线性关系,建模就会变得非常困难,特别是对于回归问题。log和exp函数可以帮助调节数据的相对比例,从而改进线性模型或神经网络的学习效果。

# 模拟的计数数据集
rnd = np.random.RandomState(0)
# np.random.RandomState.normal()生成正态分布,可以指定方差和均值
X_org = rnd.normal(size=(1000, 3))
w = rnd.normal(size=3)
#poisson()泊松分布
X = rnd.poisson(10 * np.exp(X_org))
y = np.dot(X_org, w)# 计算每个值出现的次数
print("Number of feature appearances:\n{}".format(np.bincount(X[:, 0])))# 将计数可视化:特征X[:,0]
bins = np.bincount(X[:, 0])
plt.bar(range(len(bins)), bins, color='gray')
plt.ylabel("Number of appearances")
plt.xlabel("Value")
plt.show()# 大多数的线性模型无法很好的处理这种数据
# 尝试利用这种数据拟合一个岭回归模型
from sklearn.linear_model import Ridge
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))# 应用对数变换 数值0没有意义
X_train_log = np.log(X_train + 1)
X_test_log = np.log(X_test + 1)
# 可视化::特征X[:,0]
plt.hist(X_train_log[:, 0], bins=25, color='gray')
plt.ylabel("Number of appearances")
plt.xlabel("Value")
plt.show()# 再次拟合岭回归模型
score = Ridge().fit(X_train_log, y_train).score(X_test_log, y_test)
print("Test score: {:.3f}".format(score))运行结果:
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  917  9  7 14 12  7  3  8  4  5  5  3  4  2  4  1  1  3  2  5  3  8  2  52  1  2  3  3  2  2  3  3  0  1  2  1  0  0  3  1  0  0  0  1  3  0  10  2  0  1  1  0  0  0  0  1  0  0  2  2  0  1  1  0  0  0  0  1  1  00  0  0  0  0  0  1  0  0  0  0  0  1  1  0  0  1  0  0  0  0  0  0  01  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1]
Test score: 0.622
Test score: 0.875



通常来说只有一部分特征应该进行变换,有时每个特征的变换方式也各不相同,对于基于树的模型来说,这种变换不重要,但是对于线性模型来说可能很重要。
分箱、多项式和交互项都对模型在给定数据集上的性能有很大
影响,对于复杂度较低的模型更是这样,比如线性模型和朴素贝叶斯模型。与之相反,基于树的模型通常能够自己发现重要的交互项,大多数情况下不需要显式地变换数据。其他模型,比如 SVM、最近邻和神经网络,有时可能会从使用分箱、交互项或多项式中受益,但其效果通常不如线性模型那么明显。

自动化特征选择

增加更多的特征会是得所有的模型变得复杂,增大过拟合的可能性。在添加新特征或处理一般的高维数据集时,最好将特征的数量减少到 只包含最有用的那些特征,并删除其余特征,这样会得到更好的泛化能力,更简单那的模型。有三种基本的策略:单变量统计、基于模型的选择和迭代选择。

单变量统计

我们计算每个特征和目标值之间的关系是否存在统计特性,然后选择具有最高置信度的特征。对于分类问题,这也被称为方差分析。这个测试的一个关键性质就是它们时单变量的,只单独考虑每个特征。因此如果一个特征只有在与另一个特征合并时才具有信息量,那么这个特征将被舍弃。
想要在 scikit-learn 中使用单变量特征选择,你需要选择一项测试——对分类问题通常是 f_classif(默认值),对回归问题通常是 f_regression——然后基于测试中确定的 p 值来选择一种舍弃特征的方法。所有舍弃参数的方法都使用阈值来舍弃所有 p 值过大的特征
(意味着它们不可能与目标值相关)。计算阈值的方法各有不同,最简单的是 SelectKBest和 SelectPercentile,前者选择固定数量的 k 个特征,后者选择固定百分比的特征。

# 将分类特征应用于cancer数据集
# 向数据中添加一些没有信息量的噪声特征,期待特征选择能够识别没有信息量的特征并删除
import numpy as np
from matplotlib import pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectPercentile
from sklearn.model_selection import train_test_splitcancer = 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))# 用 get_support 方法来查看哪些特征被选中,它会返回所选特征的布尔遮罩(mask)
mask = select.get_support()
print(mask)
# 将遮罩可视化——黑色为True,白色为False plt.matshow()绘制矩阵的函数
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("Sample index")
plt.show()# 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)))运行结果:
X_train.shape: (284, 80)
X_train_selected.shape: (284, 40)
[ True  True  True  True  True  True  True  True  True False  True FalseTrue  True  True  True  True  True False False  True  True  True  TrueTrue  True  True  True  True  True False False False  True False  TrueFalse False  True False False False False  True False False  True FalseFalse  True False  True False False False False False False  True FalseTrue False False False False  True False  True False False False FalseTrue  True False  True False False False False]
Score with all features: 0.919
Score with only selected features: 0.923


从这招的可视化中可以看出,大多数所选择的特征都是元素原始特征,并且大多数噪声特征都已经被删除,但是原始特征的还原并不完美。在逻辑回归上应用数据集,可以发现删除噪声可以提高性能,即使丢失了某些原始特征,如果特征太大以至于无法构建模型,或者认为多特征完全没有信息量,单变量特征的选择时有用的。

基于模型的特征选择

使用一个监督机器学习模型来判断每个特征的重要性,并且保留最重要的特征。特征选择模型需要为每个特征提供某种重要性度量,以便用这个度量对特征进行排序。决策树和基于决策树的模型提供了 feature_importances_ 属性,可以直接编码每个特征的重要性。线
性模型系数的绝对值也可以用于表示特征重要性。
与单变量选择不同,基于模型的选择同时考虑所有特征,因此可以获取交互项。

# 使用基于模型的特征选择,我们需要使用 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))
mask = select.get_support()
# 将遮罩可视化——黑色为True,白色为False
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("Sample index")
plt.show()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))运行结果:
X_train.shape: (284, 80)
X_train_l1.shape: (284, 40)
Test score: 0.930


除了两个原始特征,其余特征都被选中,由于我们指定选择40个特征,一些噪声也被选中。利用了更好的特征选择,性能也进一步得到提高。

迭代特征选择

在迭代特征选择中,将会构建一系列模型,每个模型都使用不同数量的特征。有两种基本的方法:开始时没有特征,然后逐个添加特征,直到满足某个终止条件;或者从所有特征开始,然后逐个删除特征,直到满足某个终止条件为止。其中一种方法是递归特征消除,他从所有特征开始构建模型,并根据模型舍弃最不重要的特征 ,然后使用处被舍弃特征之外的所有特征构建一个新的模型,直到仅剩下预设数量的特征,用于选择的模型需要提供某种确定特征重要性的方法。

# 递归特征消除REF
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.show()# 使用 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 score1: {:.3f}".format(score))# 利用在 RFE 内使用的模型来进行预测,这仅使用被选中的特征集
print("Test score2: {:.3f}".format(select.score(X_test, y_test)))运行结果:
Test score1: 0.930
Test score2: 0.951

书中的实验结果: RFE 内部使用的随机森林的性能,与在所选特征上训练一个 Logistic 回归模型得到的性能相同。换句话说,只要我们选择了正确的特征,线性模型的表现就与随机森林一样好。
现在这个结果,应该是特征选择的不够正确。
如果你不确定何时选择使用哪些特征作为机器学习算法的输入,那么自动化特征选择可能特别有用。它还有助于减少所需要的特征数量,加快预测速度,或允许可解释性更强的模型。在大多数现实情况下,使用特征选择不太可能大幅提升性能,但它仍是特征工程工具箱中一个非常有价值的工具。

利用专家知识

对于专家知识更正确的叫法:常识
预测家门口的自行车出租:对于给定的日期和
时间,预测有多少人将会在 Andreas 的家门口租一辆自行车——这样他就知道是否还有自行车留给他

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from sklearn.preprocessing import OneHotEncoder, PolynomialFeatures
import mglearncitibike = 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
# 给整个月租车数量的可视化
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.show()

'''
在计算机上存储日期的常用方式是使用POSIX时间
从1970年1月1日00:00:00起至到现在的总秒数
使用这个单一整数特征作为数据表示
'''
# 提取目标值(租车的数量)
y = citibike.values
# 利用"%s"将时间转换为POSIX时间 时间序列index的strftime()方法
# astype方法:通用函数,可以用于把dataframe中的任何列转换成其他类型
# reshape(-1, 1)转换成1行
X = citibike.index.astype("int64").values.reshape(-1, 1)
# 定义一个函数 将数据划分为训练集和测试集,构建模型并将结果可视化
# 使用前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 R2:{:.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))# 获取或设置当前 x 轴或 y 轴刻度位置和标签(即设置 x 或 y 轴的标 签)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,并将随机森林传入函数
from sklearn.ensemble import RandomForestRegressor
regressor = RandomForestRegressor(n_estimators=100, random_state=0)
plt.figure()
eval_on_features(X, y, regressor)
plt.show()运行结果:
test-set R2:-0.04


'''
训练集上的预测结果很好,符合随机森林通常的表现
对于测试集来说,预测结果是一条常数直线
R2是-0.04说明我们什么都没有学习到
问题在于特征和随机森林的组合。测试集中posix时间特征的值超出了训练集中特征取值的范围:
测试集中的数据点的时间戳要晚于训练集中所有数据点
树以及随机森林无法外推到训练集之外的特征范围
模型只能预测训练集中最近数据点的目标值 也就是最后一次观测到数据的时间
通过观察训练数据中的租车数量图像:一天内的时间与一周的周几是两个非常重要的因素
添加这两个特征 POSIX时间中学不到任何东西,所以删除这个特征
'''# 首先我们仅使用每天的时刻
X_hour = citibike.index.hour.values.reshape(-1, 1)
eval_on_features(X_hour, y, regressor)
plt.show()# 添加一周中的周几作为特征
X_hour_week = np.hstack([citibike.index.dayofweek.values.reshape(-1,1),citibike.index.hour.values.reshape(-1,1)])
eval_on_features(X_hour_week, y, regressor)
plt.show()# 尝试使用一个简单的模型 线性模型
from sklearn.linear_model import LinearRegression, Ridgeeval_on_features(X_hour_week, y, LinearRegression())
plt.show()



'''
效果差很多 周期性的模式也很奇怪
原因在于用整数编码一周的周几和一天内的时间,会被解释为连续变量
线性模型只能学到关于每天时间的线性函数 时间越晚 租车数量越多
'''# 将整数解释为分类变量
enc = OneHotEncoder()
X_hour_week_onehot = enc.fit_transform(X_hour_week).toarray()eval_on_features(X_hour_week_onehot,y,Ridge())
plt.show()

'''
线性模型为一周的每天都学到了一个系数
为一天内的每个时刻都学到了一个系数
一周七天共享”一天内每个时刻“
'''
#利用交互特征 让模型为星期几和时刻的每一种组合都学到一个系数
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,y,lr)
plt.show()


'''
相对于随机森林
这个模型可以很清楚的看到学习的内容 对每个星期几和时刻的交互项学到一个系数 将模型学到的系数作图
'''# # 为时刻和星期几特征创建特征名称
hour =["%0.2d"% 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(features)
features_nonzero = np.array(features_poly)[lr.coef_ != 0]
coef_nonzero= lr.coef_[lr.coef_ != 0]# 将线性模型学到的系数可视化
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.show()

总结

讨论了如何处理不同的数据类型,使用适合机器学习算哒的数据表示方式的重要性以及利用专家知识从数据中创建导出特征的可能性。特别是线性模型,可能会从分箱、添加多项式和交互项而生成的新特征中受益。对于更加复杂的非线性模型(随机森林和SVM),在无需显示扩展特征空间的前提下就可以学习更加复杂的任务。

Python机器学习基础教程7相关推荐

  1. Python机器学习基础教程-第2章-监督学习之K近邻

    前言 本系列教程基本就是摘抄<Python机器学习基础教程>中的例子内容. 为了便于跟踪和学习,本系列教程在Github上提供了jupyter notebook 版本: Github仓库: ...

  2. 回归素材(part4)--python机器学习基础教程

    学习笔记,仅供参考 文章目录 python机器学习基础教程 线性模型 用于回归的线性模型 过拟合 岭回归 lasso python机器学习基础教程 线性模型 用于回归的线性模型 过拟合

  3. 泛化,过拟合,欠拟合素材(part1)--python机器学习基础教程

    学习笔记,仅供参考,有错必纠 文章目录 python机器学习基础教程 泛化.过拟合.欠拟合 模型复杂度与训练集大小的关系 python机器学习基础教程 泛化.过拟合.欠拟合 判断一个算法在新数据上表现 ...

  4. python基础教程免费下载-《Python机器学习基础教程》高清版免费PDF下载

    Python机器学习基础教程-[德] 安德里亚斯·穆勒(Andreas C.Müller)[美]莎拉·吉多(Sarah Guido) 著,张亮(hysic) 译 下载地址1:网盘下载 下载地址2:网盘 ...

  5. Python机器学习基础教程(1)Irises(鸢尾花)分类之新手上路

    一.感谢博客的内容提供的参考 标题:最新版学习笔记---Python机器学习基础教程(1)Irises(鸢尾花)分类---附完整代码 作者:非鱼子焉 地址:https://zhu-rui.blog.c ...

  6. python机器学习基础教程-学习笔记(一)

    了解 scikit-learn 及其用法是很重要的,但还有其他一些库也可以改善你的编程体验. scikit-learn 是基于 NumPy 和 SciPy 科学计算库的.此外,我们还会用到 panda ...

  7. Python机器学习基础教程——鸢尾花分类

    Python机器学习基础教程--鸢尾花分类 初识数据 训练数据与测试数据 观察数据-数据可视化 模型的建立与评估--K近邻算法 她还有一些鸢尾花的测量数据,这些花之前已经被植物学专家鉴定为属于 set ...

  8. 最新版学习笔记---Python机器学习基础教程(1)Irises(鸢尾花)分类---附完整代码

    开始学习机器学习基础,在此留下学习心得与自己的理解. 啥也不说,先看一下鸢尾花啥样 好看吧~~~~ Iris 1.环境搭建 2.了解数据 2.1读取数据 2.2查看数据 3.分离数据 4.构建模型(k ...

  9. python自学教程读书导图-python机器学习基础教程读书笔记八(全书总结)

    全书总结 现在你知道了如何使用重要的机器学习算法进行监督学习和无监督学习,这让你可以解 决很多种机器学习问题.在带你探索机器学习提供的所有可能性之前,我们希望给你一 些最后的建议.一些额外的资源,并提 ...

  10. Python机器学习基础教程1

    文章目录 前言 一.监督学习 1.分类与回归 1.鸢尾花分类 2.构建第一个模型:k近邻算法 2.泛化,过拟合欠拟合 3.监督学习算法 1.一些样本数据集 2.K近邻 K近邻回归 优点和缺点 3.线性 ...

最新文章

  1. 携程App for Apple Watch探索
  2. 新工作总结之权限系统设计
  3. 计算机会计应用实训,计算机会计模拟实习报告.pdf
  4. 陈曦:超低延迟下的实时合唱体验升级
  5. 第五十六期:IPv6只是增加了地址数量?其实真相并没有那么简单!
  6. 帆软报表学习笔记——根据参数查询
  7. 【Python笔记】网络编程
  8. 如何用cv2.imread()读取falsk接收的图片
  9. 昆仑通态触摸屏数据转发上传_说说昆仑通态(MCGS)的数组功能
  10. linux搜索word文档内容,在目录中搜索MS word文件以获取Linux中的特定内容
  11. 【Zigbee】进阶篇(2) Zigbee协议栈中OSAL的执行流程
  12. Java物联网中间件_物联网中间件技术——Niagara介绍.pdf
  13. 学习笔记--爬虫(11)---js逆向思路和解密思路(初级)
  14. 男人不想打工一辈子,再忙也要读这10本书,提高情商智商
  15. 英文建站必备:Namesilo 购买注册流程
  16. 国内哪家虚拟主机比较好
  17. 利用一个竞态漏洞root三星s8的方法
  18. 查询同名同性学生名单,并统计同名人数
  19. 手把手教你用EZDML批量生成vue-element-admin前端页面代码
  20. 自学Java的心路历程

热门文章

  1. hadoop+zookeeper+hbase+hive
  2. Google靠什么做到不作恶? 节选
  3. DirectX9.0 入门手册(6)
  4. 书论75 钱泳《书学》
  5. 基于HTML(品优购)电商项目项目的设计与实现(html前端源码和论文设计)
  6. 2017年Android百大第三方框架排行榜
  7. 2022年西安市施工员(装修装饰)考试练习题及答案
  8. SpringMVC操作全攻略
  9. python网页优化_李亚涛:python辅助seo优化让你更轻松
  10. JS 三元运算符嵌套使用