特徵選擇與特徵工程初探
特徵工程是機器學習的第一步,涉及清理現有資料集、提高信噪比和降低維數的所有技術。大多數演算法對輸入資料有很強的假設,當使用原始資料集時,它們的效能可能會受到負面影響。
另外有些特徵之間高度相關,在其中一個特徵提供了足夠的資訊之後,與之相關的其他特徵往往無法提供額外的資訊。這時我們就需要了解如何減少特徵數量或者僅選擇最佳特徵。
一、scikit-learn資料集
scikit-learn提供了一些用於測試的內建資料集,這些資料集包含在sklearn。datasets中,每個資料集都包含了輸入集(特徵集)X和標籤(目標值)y。比如波士頓房價的資料集(用於迴歸問題):
from
sklearn。datasets
import
load_boston
boston
=
load_boston
()
X
=
boston
。
data
y
=
boston
。
target
(
‘特徵集的shape:’
,
X
。
shape
)
(
‘目標集的shape:’
,
y
。
shape
)
特徵集的
shape
:
(
506
,
13
)
目標集的
shape
:
(
506
,)
可以看到,這個資料集包含了506個樣本、13個特徵,以及1個目標值。
假如我們不想使用scikit-learn提供的資料集,那麼我們還可以使用scikit-learn提供的工具來手動建立特定的資料集。相關的方法有:
make_classification()
:用於建立適用於測試分類演算法的資料集;
make_regression()
:用於建立適用於測試迴歸模型的資料集;
make_blobs()
:用於建立適用於測試聚類演算法的資料集。
二、建立訓練集和測試集
一般來說,我們要在正式應用我們訓練的模型前對它進行測試。因此我們需要將資料集分為訓練集和測試集,顧名思義,前者用於訓練模型引數,後者用於測試模型效能。在某些情況下,我們甚至還會再分出一個數據集作為交叉驗證集,這種處理方式適用於有多種模型可供選擇的情況。
資料集的分割有一些注意事項:首先,兩個資料集必須要能反映原始資料的分佈,否則在資料集失真的情況下得到的模型對於真實樣本的預測效力會比較差;其次,原始資料集必須在分割之前隨機混合,以避免連續元素之間的相關性。
在scikit-learn中,我們可以使用
train_test_split()
函式來快速實現資料集的分割。
from
sklearn。model_selection
import
train_test_split
X_train
,
X_test
,
y_train
,
y_test
=
train_test_split
(
X
,
y
,
test_size
=
0。25
,
random_state
=
1000
)
這裡前兩個位置引數分別是特徵集和目標集,
test_size
用於指定測試集大小佔整個資料集的比例,
random_state
則是指定一個隨機種子,這樣可以確保我們在重複試驗時資料不會發生變化(資料集都變了,那模型效果的變化就不知道該歸因於模型的最佳化還是歸因於資料集的變化了。)
三、管理分類資料
在許多分類問題中,目標資料集由各種類別標籤組成。但是很多演算法是不支援這種資料格式的,因此我們要對其進行必要的編碼。
假設我們有一個由10個樣本組成的資料集,每個樣本有兩個特徵。
import
numpy
as
np
X
=
np
。
random
。
uniform
(
0。0
,
1。0
,
size
=
(
10
,
2
))
y
=
np
。
random
。
choice
((
‘Male’
,
‘Female’
),
size
=
(
10
))
(
‘X:’
,
X
)
(
‘y:’
,
y
)
X
:
[[
0。48463048
0。21682675
]
[
0。27987595
0。28061459
]
[
0。13723177
0。45159025
]
[
0。42727284
0。99834867
]
[
0。61113219
0。31892401
]
[
0。14985227
0。71565914
]
[
0。048201
0。49254257
]
[
0。54466226
0。8419817
]
[
0。94426201
0。78924785
]
[
0。36877342
0。53250431
]]
y
:
[
‘Female’
‘Female’
‘Male’
‘Female’
‘Female’
‘Female’
‘Male’
‘Male’
‘Female’
‘Male’
]
1。 使用
LabelEncoder
類
from
sklearn。preprocessing
import
LabelEncoder
le
=
LabelEncoder
()
yt
=
le
。
fit_transform
(
y
)
(
y
)
(
yt
)
(
le
。
classes_
)
[
‘Female’
‘Female’
‘Male’
‘Female’
‘Female’
‘Female’
‘Male’
‘Male’
‘Female’
‘Male’
]
[
0
0
1
0
0
0
1
1
0
1
]
[
‘Female’
‘Male’
]
獲得逆變換的方法很簡單:
output
=
[
1
,
0
,
1
,
1
,
0
,
0
]
decoded_output
=
[
le
。
classes_
[
i
]
for
i
in
output
]
(
decoded_output
)
[
‘Male’
,
‘Female’
,
‘Male’
,
‘Male’
,
‘Female’
,
‘Female’
]
這種方法很簡單,但是有個缺點:所有的標籤都變成了數字,然後使用真實值的分類器會根據其距離考慮相似的數字,而忽略其代表的分類含義。因此我們通常優先選擇獨熱編碼(one-hot encoding,又稱一次有效編碼),將資料二進位制化。
2。 使用
LabelBinarizer
類
from
sklearn。preprocessing
import
LabelBinarizer
lb
=
LabelBinarizer
()
yb
=
lb
。
fit_transform
(
y
)
(
y
)
(
yb
)
(
lb
。
inverse_transform
(
yb
))
[
‘Female’
‘Female’
‘Male’
‘Female’
‘Female’
‘Female’
‘Male’
‘Male’
‘Female’
‘Male’
]
[[
0
]
[
0
]
[
1
]
[
0
]
[
0
]
[
0
]
[
1
]
[
1
]
[
0
]
[
1
]]
[
‘Female’
‘Female’
‘Male’
‘Female’
‘Female’
‘Female’
‘Male’
‘Male’
‘Female’
‘Male’
]
可以看到,這裡我們可以使用
LabelBinarizer
類的
inverse_transform
方法進行逆轉化。
當存在多個標籤時,這種方法會將其中一個標籤變換為1,其餘標籤全部為0。這可能會導致的問題顯而易見,也就是我們將多分類問題轉換成了二分類問題。
四、管理缺失特徵
我們可能會經常碰見資料缺失的情況,有以下選項可以解決該問題:
刪除整行:這個選項比較激進,一般只有當資料集足夠大、缺少的特徵值數量很多而且預測風險大時才會選擇;
建立子模型來預測這些特徵值:第二個選項實現起來比較困難,因為需要確定一個監督策略來訓練每個特徵的模型,最後預測它們的值;
使用自動策略根據其他已知值插入這些缺失的特徵值:考慮到以上的利弊,這可能是最好的選項了。
from
sklearn。preprocessing
import
Imputer
data
=
np
。
array
([[
1
,
np
。
nan
,
2
],
[
2
,
3
,
np
。
nan
],
[
-
1
,
4
,
2
]])
# 插入均值
imp
=
Imputer
(
strategy
=
‘mean’
)
(
‘Mean:
\n
’
,
imp
。
fit_transform
(
data
))
# 插入中位數
imp
=
Imputer
(
strategy
=
‘median’
)
(
‘Median:
\n
’
,
imp
。
fit_transform
(
data
))
# 插入眾數
imp
=
Imputer
(
strategy
=
‘most_frequent’
)
(
‘Mode:
\n
’
,
imp
。
fit_transform
(
data
))
Mean
:
[[
1。
3。5
2。
]
[
2。
3。
2。
]
[
-
1。
4。
2。
]]
Median
:
[[
1。
3。5
2。
]
[
2。
3。
2。
]
[
-
1。
4。
2。
]]
Mode
:
[[
1。
3。
2。
]
[
2。
3。
2。
]
[
-
1。
4。
2。
]]
五、資料縮放和歸一化
一般的資料集是由不同的值組成的,可以從不同的分佈得到且具有不同的尺度,有時還會有異常值。當不同特徵的取值範圍差異過大時,很可能會對模型產生不良影響。因此我們往往需要先規範資料集。
我們來對比一下原始資料集和經過縮放和中心化的資料集:
from
sklearn。preprocessing
import
StandardScaler
from
sklearn。datasets
import
load_iris
import
seaborn
as
sns
import
matplotlib。pyplot
as
plt
sns
。
set
()
# 匯入資料
iris
=
load_iris
()
data
=
iris
。
data
# 繪製原始資料散點圖
fig
,
axes
=
plt
。
subplots
(
1
,
2
,
figsize
=
(
10
,
5
))
sns
。
scatterplot
(
x
=
data
[:,
0
],
y
=
data
[:,
1
],
ax
=
axes
[
0
])
# 資料歸一化
scaler
=
StandardScaler
()
scaled_data
=
scaler
。
fit_transform
(
data
)
# 繪製規範化資料散點圖
sns
。
scatterplot
(
x
=
scaled_data
[:,
0
],
y
=
scaled_data
[:,
1
],
ax
=
axes
[
1
])
plt
。
setp
(
axes
,
xlim
=
[
-
2
,
8
],
ylim
=
[
-
3
,
5
]);
可以看到,我們的資料分佈形態沒有變化,但是資料的分佈範圍卻變了。我們將資料轉化成了均值為0(幾乎為0),標準差為1的歸一化資料。
(
‘轉化前均值:
\n
’
,
np
。
mean
(
data
,
axis
=
0
))
(
‘轉化後均值:
\n
’
,
np
。
mean
(
scaled_data
,
axis
=
0
))
(
‘轉化前方差:
\n
’
,
np
。
std
(
data
,
axis
=
0
))
(
‘轉化後方差:
\n
’
,
np
。
std
(
scaled_data
,
axis
=
0
))
轉化前均值:
[
5。84333333
3。054
3。75866667
1。19866667
]
轉化後均值:
[
-
1。69031455e-15
-
1。63702385e-15
-
1。48251781e-15
-
1。62314606e-15
]
轉化前方差:
[
0。82530129
0。43214658
1。75852918
0。76061262
]
轉化後方差:
[
1。
1。
1。
1。
]
在資料縮放時,我們還可以使用類
RobustScaler
對異常值進行控制和選擇分位數範圍。
from
sklearn。preprocessing
import
RobustScaler
# 轉化資料1
rb1
=
RobustScaler
(
quantile_range
=
(
15
,
85
))
scaled_data1
=
rb1
。
fit_transform
(
data
)
# 轉化資料2
rb2
=
RobustScaler
(
quantile_range
=
(
25
,
75
))
scaled_data2
=
rb2
。
fit_transform
(
data
)
# 轉化資料3
rb3
=
RobustScaler
(
quantile_range
=
(
30
,
60
))
scaled_data3
=
rb3
。
fit_transform
(
data
)
# 繪製散點圖
fig
,
axes
=
plt
。
subplots
(
2
,
2
,
figsize
=
(
10
,
10
))
sns
。
scatterplot
(
x
=
data
[:,
0
],
y
=
data
[:,
1
],
ax
=
axes
[
0
,
0
])
sns
。
scatterplot
(
x
=
scaled_data1
[:,
0
],
y
=
scaled_data1
[:,
1
],
ax
=
axes
[
0
,
1
])
sns
。
scatterplot
(
x
=
scaled_data2
[:,
0
],
y
=
scaled_data2
[:,
1
],
ax
=
axes
[
1
,
0
])
sns
。
scatterplot
(
x
=
scaled_data3
[:,
0
],
y
=
scaled_data3
[:,
1
],
ax
=
axes
[
1
,
1
])
plt
。
setp
(
axes
,
ylim
=
[
-
4
,
5
],
xlim
=
[
-
2
,
8
]);
可以看到,資料的大致分佈形態仍然很接近,但是資料的分佈範圍簡直大變樣。另外,由於我們設定了不同的分位數範圍,因此資料的樣本量也不太一樣。
常用的還有
MinMaxScaler
和
MaxAbsScaler
,前者透過刪除不屬於給定範圍的元素,後者則透過考慮使用最大絕對值來縮放資料。
scikit-learn還為每個樣本規範化提供了一個類:
Normalizer
。它可以對資料集的每個元素應用Max、L1和L2範數。
Max:每個值都除以資料集中的最大值;
L1:每個值都除以資料集中所有值的絕對值之和;
L2:每個值都除以資料集中所有值的平方和的平方根
我們來看一個例子。
from
sklearn。preprocessing
import
Normalizer
# 生成資料
data
=
np
。
array
([
1
,
2
])
。
reshape
(
1
,
2
)
(
‘原始資料:’
,
data
)
# Max
n_max
=
Normalizer
(
norm
=
‘max’
)
(
‘Max:’
,
n_max
。
fit_transform
(
data
))
# L1範數
n_l1
=
Normalizer
(
norm
=
‘l1’
)
(
‘L1範數:’
,
n_l1
。
fit_transform
(
data
))
# L2範數
n_l2
=
Normalizer
(
norm
=
‘l2’
)
(
‘L2範數:’
,
n_l2
。
fit_transform
(
data
))
原始資料:
[[
1
2
]]
Max
:
[[
0。5
1。
]]
L1範數
:
[[
0。33333333
0。66666667
]]
L2範數
:
[[
0。4472136
0。89442719
]]
六、特徵選擇和過濾
不是所有的特徵都能提供足夠的資訊的,甚至有些特徵會對我們的模型訓練產生障礙,因此在模型訓練開始前我們要對特徵做出一定的選擇。
接下來我們使用
SelectKBest
方法結合F檢驗來篩選迴歸模型的特徵。
from
sklearn。feature_selection
import
SelectKBest
,
f_regression
from
sklearn。datasets
import
load_boston
boston
=
load_boston
()
(
‘Boston data shape: ’
,
boston
。
data
。
shape
)
selector
=
SelectKBest
(
f_regression
)
X_new
=
selector
。
fit_transform
(
boston
。
data
,
boston
。
target
)
(
‘Filtered Boston data shape:’
,
X_new
。
shape
)
(
‘F-Scores:’
,
selector
。
scores_
)
Boston
data
shape
:
(
506
,
13
)
Filtered
Boston
data
shape
:
(
506
,
10
)
F
-
Scores
:
[
88。15124178
75。2576423
153。95488314
15。97151242
112。59148028
471。84673988
83。47745922
33。57957033
85。91427767
141。76135658
175。10554288
63。05422911
601。61787111
]
然後我們使用
SelectPercentile
結合卡方檢驗來篩選分類模型的特徵。
from
sklearn。feature_selection
import
SelectPercentile
,
chi2
from
sklearn。datasets
import
load_iris
iris
=
load_iris
()
(
‘Boston data shape: ’
,
iris
。
data
。
shape
)
selector
=
SelectPercentile
(
chi2
,
percentile
=
15
)
X_new
=
selector
。
fit_transform
(
iris
。
data
,
iris
。
target
)
(
‘Filtered Boston data shape:’
,
X_new
。
shape
)
(
‘F-Scores:’
,
selector
。
scores_
)
Boston
data
shape
:
(
150
,
4
)
Filtered
Boston
data
shape
:
(
150
,
1
)
F
-
Scores
:
[
10。81782088
3。59449902
116。16984746
67。24482759
]
在資料預處理時,我們還經常會採用主成分分析等方法來實現資料降維等目的,不過這一部分我們完全可以單獨拆出一個章節來講解,感興趣的朋友可以關注下後續的更新。