特征工程(四)特征抽取

特征抽取与特征选择在功能上类似,都最终实现了数据集特征数量的减少,但特征选择得到的是原有特征的子集,而特征抽取是将原有特征根据某种函数关系转换为新的特征,并且数据集维度比原来的低。两者所得到的的特征集合与原特征集合对应关系不同。

特征抽取

4.1 无监督特征抽取

实现无监督特征抽取的算法有很多,这里仅以“主成分分析”和“因子分析”为例给予介绍

主成分分析 PAC

在一般的文献资料中,谈到“降维”,必然会介绍“主成分分析PCA”,因为PCA是实现降维的典型方法。

通过将n维的数据集降维到n'低纬度空间;使得降维之后数据集尽可能的代表原数据集同时降维之后的损失尽可能的小。

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
X[: 4]

在经典的鸢尾花数据中,X有4个特征,对这4个特征进行主成分分析

from sklearn.decomposition import PCA
import numpy as np
pca = PCA()    # 
X_pca = pca.fit_transform(X)   
np.round(X_pca[: 4], 2)    # 取2位小数

array([[-2.68, 0.32, -0.03, -0. ],
[-2.71, -0.18, -0.21, -0.1 ],
[-2.89, -0.14, 0.02, -0.02],
[-2.75, -0.32, 0.03, 0.08]])

数据经过PCA之后的结果,显然不是原来数据子集,是根据某种映射关系得到的结果

PCA算法可以通过协方差实现,在这里默认使用奇异值分解

这里的输出结果并没有降维,依然是4个特征,这是因为在创建模型时没有设置所需要的维度数目

所谓主成分分析,就是找出主成分—主要维度。

pca.explained_variance_ratio_

array([0.92461872, 0.05306648, 0.01710261, 0.00521218])

以模型的属性explained_variance_ratio_得到各个特征的可解释方差比例,比例越高的,其特征就越重要。如果只保留2个特征(或者降维到2个特征),结果就很明显了。

pca = PCA(n_components=2)    
X_pca = pca.fit_transform(X)
X_pca[: 4]

array([[-2.68412563, 0.31939725],
[-2.71414169, -0.17700123],
[-2.88899057, -0.14494943],
[-2.74534286, -0.31829898]])

所得X_pca相对原来的数据集X实现了降维,并且得到的特征是原数据特征的“主要成分”——所保留下来的两个特征的可解释方差比例达到了0.98,可以说是非常“主要”的。

pca.explained_variance_ratio_.sum()

0.9776852063187949

在上述演示PCA的过程中,参数中只用到数据集X,这就显示了它的“无监督”特点。

经过PCA降维的数据,与原数据相比,会不会对机器学习模型的最终效果有影响?

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(X, iris.target,
                                                   test_size=0.3, 
                                                    random_state=0)
clf = DecisionTreeClassifier() # 决策树分类器
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)


# X_pca只利用2个维度的数据训练模
X_train_pca, X_test_pca, y_train_pca, y_test_pca = train_test_split(X_pca, iris.target,
                                                   test_size=0.3, 
                                                    random_state=0)
clf2 = DecisionTreeClassifier()
clf2.fit(X_train_pca, y_train_pca)
y_pred_pca = clf2.predict(X_test_pca)
accuracy2 = accuracy_score(y_test_pca, y_pred_pca)

print("dataset with 4 features: ", accuracy)
print("dataset with 2 features: ", accuracy2)

在使用DecisionTreeClassifier模型训练和预测了原始数据及降维之后的数据,利用只有2个维度的数据训练的模型,其预测效果相对来讲也是说的过去的,此结果也表明这个2个维度的确是“主成分”

将鸢尾花数据集中原来的4个特征(sepal length、sepal width、petal length和petal width)用PCA技术抽取2个特征,并用图示表示特征抽取之后的分类效果。

from sklearn import datasets
import pandas as pd

# iris = datasets.load_iris()
# df = pd.DataFrame(data=iris.data,columns=iris.feature_names)
# df["target"] = iris.target

url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"    # 练习通过URL获得数据的方法
df = pd.read_csv(url, names=['sepal length','sepal width','petal length','petal width','target'])
from sklearn.preprocessing import StandardScaler

features = ['sepal length', 'sepal width', 'petal length', 'petal width']
# Separating out the features
x = df.loc[:, features].values
# Separating out the target
y = df.loc[:,['target']].values
# 标准化特征
x = StandardScaler().fit_transform(x)
from sklearn.decomposition import PCA

pca = PCA(n_components=2)     #PCA降维
principalComponents = pca.fit_transform(x)
principalDf = pd.DataFrame(data = principalComponents
             , columns = ['principal component 1', 'principal component 2'])

finalDf = pd.concat([principalDf, df[['target']]], axis = 1)
finalDf
principal component 1 principal component 2 target
0 -2.264542 0.505704 Iris-setosa
1 -2.086426 -0.655405 Iris-setosa
2 -2.367950 -0.318477 Iris-setosa
3 -2.304197 -0.575368 Iris-setosa
4 -2.388777 0.674767 Iris-setosa
... ... ... ...
145 1.870522 0.382822 Iris-virginica
146 1.558492 -0.905314 Iris-virginica
147 1.520845 0.266795 Iris-virginica
148 1.376391 1.016362 Iris-virginica
149 0.959299 -0.022284 Iris-virginica

150 rows × 3 columns

import matplotlib.pyplot as plt

fig = plt.figure(figsize = (8,8))
ax = fig.add_subplot(1,1,1) 
ax.set_xlabel('Principal Component 1', fontsize = 15)
ax.set_ylabel('Principal Component 2', fontsize = 15)
ax.set_title('2 component PCA', fontsize = 20)

targets = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
colors = ['r', 'g', 'b']
for target, color in zip(targets,colors):
    indicesToKeep = finalDf['target'] == target
    ax.scatter(finalDf.loc[indicesToKeep, 'principal component 1']
               , finalDf.loc[indicesToKeep, 'principal component 2']
               , c = color
               , s = 50)
ax.legend(targets)
ax.grid()

因子分析 FA

因子分析也是实现特征抽取(降维)的一种方式。对于数据集的特征而言,几个特征背后可能有共同的某个原因,这个原因称为“因子”。

比如,“失业率”“消费指数”“幸福指数”这三个特征的数值变换,都与“经济发展状况”有关,而“经济发展状况”用“CDP”衡量,那么“GDP”就是这三个特征潜在的共同因子。

from sklearn.decomposition import FactorAnalysis
fa = FactorAnalysis()    
iris_fa = fa.fit(iris.data)
fa.components_    

array([[ 0.70698856, -0.15800499, 1.65423609, 0.70084996],
[ 0.115161 , 0.15963548, -0.04432109, -0.01403039],
[-0. , 0. , 0. , 0. ],
[-0. , 0. , 0. , -0. ]])

FactorAnalysis模块与使用PCA模块的方法一样,返回的是潜在因子与每个特征的方差。 由于没有约定潜在的维度,所以默认以数据集的原有特征数为降维之后的特征数。因为鸢尾花数据集原来有4个特征,所以经过因子分析之后的特征,即潜在的共同因子有4个。而返回值显示,只有两个潜在因子与各个特征有相关性——虽然这里还没有命名,也没有必要命名这两潜在因子,只要找出来即可。

fa = FactorAnalysis(n_components=2)
iris_two = fa.fit_transform(iris.data)
iris_two[: 4]

array([[-1.32761727, -0.56131076],
[-1.33763854, -0.00279765],
[-1.40281483, 0.30634949],
[-1.30104274, 0.71882683]])

都是对鸢尾花数据集进行特征抽取(降维),PCA所得与FA所得结果不同:

  • PCA对原特征通过线性变换之后实现特征抽取(降维);FA通过对原特征的潜在共同因子分析实现特征抽取(降维)
  • PCA没有设置任何前提假设,所有的特征都可以借助某种映射关系实现降维;FA只能适用于某些特征之间有某种相关的假设之下,否则就找不到共同因子
%matplotlib inline
import matplotlib.pyplot as plt
f = plt.figure(figsize=(5, 5))
ax = f.add_subplot(111)
ax.scatter(iris_two[:,0], iris_two[:, 1], c=iris.target)
ax.set_title("Factor Analysis 2 Components")

Text(0.5, 1.0, 'Factor Analysis 2 Components')

这里用图示的方式显示了FA对鸢尾花数据集的分类效果。与PCA进行对比,通过图示显示器分类效果。

f = plt.figure(figsize=(5, 5))
ax = f.add_subplot(111)
ax.scatter(X_pca[:,0], X_pca[:, 1], c=iris.target)
ax.set_title("PCA 2 Components")
Text(0.5, 1.0, 'PCA 2 Components')

有资料认为,PCA可以视为FA的一种特例。

对于本节阐述的无监督特征抽取,除了主成分分析和因子分析,还有奇异值分解、字典学习、多维缩放、核主成分分析、等度量映射等。

4.2 有监督特征抽取

基础知识

上节所述的特征抽取方法,在某种程度上已经能满足很多常见的应用。但是必须满足其适用条件时才是正确的,因此才出现了各种算法,它们分别解决不同的问题。

from sklearn.datasets import make_classification

X,y = make_classification(n_samples=1000,
                          n_features=4,
                          n_redundant=0,
                          n_classes=3,  # 数据分三个类别
                          n_clusters_per_class=1,
                          class_sep=0.5,
                          random_state=10)
X.shape, y.shape

((1000, 4), (1000,))

利用make_classification函数创建了一个可以用来分类的数据集。如果采用主成分分析PCA对于数据集X进行特征抽取,可找出最具代表性的两个特征。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y)

用不同颜色代表不同类别。

经过PCA之后得到了具有两个维度的数据,应该在图中非常明显地表现为三个类别,然而,眼前的结果是这些点的分布乱作一团,看不出什么类别。

显然PCA在这里失效了,不得不使用新方法。

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X, y)
plt.scatter(X_lda[:, 0], X_lda[:, 1], c=y)

在这里引入了LinearDiscriminantAnalysis模块,这是机器学习中常用的“线性判别分析LDA”

线性判别分析LDA与主成分分析PCA的不同之处是,LDA需要输入的不仅要X,还要有y,因此它被称为“有监督特征提取”

项目案例

针对鸢尾花数据,使用线性判别分析方法,实现有监督特征抽取,并对PCA和LDA方法的适用性进行分析。

from sklearn import datasets
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
iris = datasets.load_iris()
X_iris, y_iris= iris.data, iris.target
lda = LinearDiscriminantAnalysis(n_components=2)
X_iris_lda = lda.fit_transform(X_iris, y_iris)
plt.scatter(X_iris_lda[:, 0], X_iris_lda[:, 1], c=y_iris)

从显示结果来看,对于鸢尾花数据集,LDA同样能抽取到很好的特征。或者说,无论是有监督还是无监督的特征抽取,都能用于鸢尾花数据集。

而前面所创造的数据集则不然,PCA对它无能为力,只能用LDA,其原因应该从每个方法的原理和数据特点去考虑。在实践中,通常要看一下数据均值和方差。

import numpy as np
X_mean = []
X_var = []
for i in range(4):
    m = []
    v = []
    for j in range(3):
        m.append(np.mean(X[:, i][j==y]))
        v.append(np.var(X[:, i][j==y]))
    X_mean.append(v)#X_var = np.var(X, axis=0)
    X_var.append(m)
print("X_mean: ", X_mean)
print("X_var: ", X_var)

X_mean: [[0.2183712580305977, 1.1052626159191397, 0.4057388127038256], [1.061062843407683, 1.050045165331185, 1.0287851089775246], [0.9874712352883422, 0.9682431042431385, 1.0036869489851274], [0.7034263353290686, 0.5892565808874551, 1.0390892157973233]]
X_var: [[-0.5278067685825016, -0.5169064671104584, 0.4672757741731266], [0.061662385983550616, -0.17231056591994068, 0.08946387250298174], [-0.014067167509643446, -0.07051190168141928, 0.0039673167895780004], [0.45836039396857386, -0.46246572782720174, 0.5115950547496408]]

这里得到的是每个特征不同类别数据的平均值和方差。直接观察上面的结果,难以看出门道。下面以第一个特征的数据为例。

X_mean[0]

[0.2183712580305977, 1.1052626159191397, 0.4057388127038256]

X_var[0]

[-0.5278067685825016, -0.5169064671104584, 0.4672757741731266]

这里显示的是该特征下三个类别数据的平均值和方差。通过作图进一步观察:

fig, axs = plt.subplots()
for i in range(3):
    axs.axhline(X_var[0][i], color='red')
    axs.axvline(X_mean[0][i], color='blue', linestyle="--")
axs.scatter(X_mean[0], X_var[0], marker="D")

在输出的图示中,横轴数据显示的是平均值,纵轴数据表示的是方差。

显然,选择平均值对此特征进行分类要优于方差。而LDA则是依赖均值、PCA依赖方差对数据进行分类,这就是显示不同效果的原因。

此外,在使用LDA的时候,还要注意参数n_components的取值范围,不要大于数据维度-1。

LDA不仅仅是特征抽取的方法,也可以作为类似于线性回归等有监督学习的模型。

动手练习

对wine_data数据,利用线性判别分析实现特征抽取

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

df_wine = pd.read_csv("datasets/wine_data.csv")
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

std = StandardScaler()
X_train_std = std.fit_transform(X_train)
X_test_std = std.transform(X_test)
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.linear_model import LogisticRegression

lda = LDA(n_components=2)
X_train_lda = lda.fit_transform(X_train_std, y_train)

lr = LogisticRegression() #逻辑回归分类
lr = lr.fit(X_train_lda, y_train)
from mlxtend.plotting import plot_decision_regions
plot_decision_regions(X_train_lda, y_train, clf=lr) 
plt.xlabel('LD 1') 
plt.ylabel('LD 2') 
plt.legend(loc='lower left') 

在测试集上的表现

X_test_lda = lda.transform(X_test_std)
plot_decision_regions(X_test_lda, y_test, clf=lr) 
plt.xlabel('LD 1') 
plt.ylabel('LD 2') 
plt.legend(loc='lower left') 

posted @ 2022-06-09 21:28  王陸  阅读(446)  评论(0编辑  收藏  举报