特徵工程(四)特徵抽取

特徵抽取與特徵選擇在功能上類似,都最終實現了數據集特徵數量的減少,但特徵選擇得到的是原有特徵的子集,而特徵抽取是將原有特徵根據某種函數關係轉換爲新的特徵,並且數據集維度比原來的低。兩者所得到的的特徵集合與原特徵集合對應關係不同。

特徵抽取

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') 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章