维度过高会造成数据稀疏,即所谓的“维灾难”,不利于模型的训练。我们在选取特征时需要遵守的一个准则就是:特征并非越多越好,而是越准确越好。
特征缩放
特征缩放是对数据的大小进行调整。关于数据“标准化”、“归一化”的理解众说纷纭,对“normalization”的理解一直模糊不清。而且特征缩放也容易滥用,虽然特征缩放对大多数机器学习算法而言更加稳健,但是如果不了解数据,一上来就标准化有时会适得其反。
特征缩放主要分成两类:
- 归一化(normalize):将数据视为向量,再将向量除以其范数(通常采用L2范数),有量纲,对应sklearn中normalize方法。针对数据集中单个样本进行缩放,适合依赖样本间相似性的算法。因会改变数据集中特征数值的分布,不适合依赖特征预测的算法。
- 标准化(scale/standardize):对数据大小按照标准方法进行调整,使值位于特定范围内,无量纲。针对数据集中单个特征进行缩放。
- Min-Max标准化方法:最大-最小值区间缩放,即减去最小值,再除以极差。标准化后数据位于[0,1]内,无量纲,对应sklearn中minmax_scale方法。
- Z-score标准化方法:使用较多,通常直接称为标准化方法(standard),即减去均值,再除以标准差。标准化后数据均值为0,标准差为1,无量纲,对应sklearn中scale方法。
如何挑选标准化方法?
- 一般概率模型(决策树等)不需要进行标准化,梯度优化模型需要(例如SVM)
- 若度量样本距离方法采用二次型(例如内积),则可选择Normalization
- 若需要特征集内特征拥有不同量纲,且部分“明显重要”的特征方差相对较小,可采用MinMaxScaler,可实现无量纲,且防止方差过小特征得不到有效利用
- z-score与Min-Max一样可实现无量纲化,但若特征变量明显不符合正态分布,可能会造成较大偏差。
注意:使用z-score前最好对特征变量可视化,观察变量是否服从正态分布。
from sklearn.preprocessing import scale, minmax_scale, normalize
Xn = normalize(X) # 归一化,对应Normalizer类
Xs = scale(X) # z-score标准化,对应StandardScaler
Xm = minmax_scale(X) # min-max标准化,对应MinMaxScaler
挑选特征(filter)
机理分析
根据业务知识,进行机理分析,挑选对目标影响较大的特征。这一步尤为关键。
方差分析
方差一定程度上代表了该特征变异程度,方差过小的特征确定性高,不具分析价值,可考虑排除。
相关性分析
对特征与目标间以及特征间进行相关性分析、偏相关分析,排除冗余特征。需要注意的是很多传统统计检验方法(例如F检验)仅能发现线性相关关系。
- 皮尔逊相关系数:适合定量特征的相关性分析。
- 偏相关分析:特征间可能存在复共线性关系,例如Fea1和Fea2都可由剩余特征线性表示,二者偏相关系数较小,那么Fea1和Fea2可能是冗余的。涉及多因变量问题时,可考虑典型相关分析。
- 卡方检验:适合定性特征与定性目标的相关性分析。
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_iris
import numpy as np
def partial_corr(y, x, z, normalize=False):
"""
计算偏相关系数
:param y: 考虑变量1
:param x: 考虑变量2
:param z: 其他所有变量
:param normalize: 线性回归是否正则化
:return: x与y的偏相关系数
"""
reg1 = LinearRegression(normalize=normalize)
reg2 = LinearRegression(normalize=normalize)
reg1.fit(z, y)
reg2.fit(z, x)
residue1 = y - reg1.predict(z)
residue2 = x - reg2.predict(z)
return np.corrcoef(residue1.T, residue2.T)[0, 1]
data = load_iris()
X = data.data
y = data.target
Xk = SelectKBest(chi2, k=3).fit_transform(X, y) # 依据卡方检验,挑选3个与target相关性最大的特征
print(partial_corr(X[:, 0], X[:, 1], X[:, 2:]))
互信息法
相比很多的统计检验(例如F检验),互信息能检测出任何的相关关系。
互信息原本是用于计算离散变量X,Y间得相关关系,定义如下:
M
I
(
X
,
Y
)
=
H
(
X
)
−
H
(
X
∣
Y
)
=
H
(
X
)
+
H
(
Y
)
−
H
(
X
,
Y
)
=
∑
p
(
x
,
y
)
l
o
g
p
(
x
,
y
)
p
(
x
)
p
(
y
)
MI(X, Y) = H(X)-H(X|Y)=H(X)+H(Y)-H(X,Y)=\sum{p(x,y)log\frac{p(x, y)}{p(x)p(y)}}
MI(X,Y)=H(X)−H(X∣Y)=H(X)+H(Y)−H(X,Y)=∑p(x,y)logp(x)p(y)p(x,y)即X的熵减去基于Y的X条件熵。
若直接采用上述方法计算连续变量间或离散变量与连续变量间的MI,会造成比较达的误差。需要将连续变量离散化,可采用"binning"(装箱)方法以及应用
k
k
k近邻方法。根据Brian的实验,采用"binning"方法时,MI值受样本个数的影响将很大。Alexander提出结合
k
k
k近邻方法来估算连续变量间的MI,定义如下(见参考资料Estimating mutual information):
设X,Y为连续变量,计算
N
N
N个样本点的k近邻样本及相应的
k
k
kth距离(切比雪夫距离)。映射到X轴一维空间中,统计每个样本
k
k
kth距离内样本个数
N
x
i
N^i_x
Nxi, 同理计算得到
N
y
i
N^i_y
Nyi,则
M
I
(
X
,
Y
)
=
ψ
(
N
)
+
ψ
(
k
)
−
⟨
ψ
(
N
x
+
1
)
+
ψ
(
N
y
+
1
)
⟩
MI(X,Y)=\psi(N)+\psi(k)-\langle \psi(N_x+1)+\psi(N_y+1)\rangle
MI(X,Y)=ψ(N)+ψ(k)−⟨ψ(Nx+1)+ψ(Ny+1)⟩其中,
ψ
\psi
ψ是digamma函数
⟨
⟩
\langle\rangle
⟨⟩代表求均值。
Brian基于上述思想给出离散变量X与连续变量Y间MI的计算公式(见参考资料Mutual Information between Discrete and Continuous Data Sets):
M
I
(
X
,
Y
)
=
ψ
(
N
)
+
ψ
(
k
)
−
⟨
ψ
(
m
)
⟩
−
⟨
ψ
(
N
x
)
⟩
MI(X,Y)=\psi(N)+\psi(k)-\langle \psi(m) \rangle - \langle \psi(N_x)\rangle
MI(X,Y)=ψ(N)+ψ(k)−⟨ψ(m)⟩−⟨ψ(Nx)⟩即先不考虑X,将所有样本映射到Y轴一维空间,统计每个样本
k
k
kth距离内样本个数
m
m
m, 再根据离散变量X的取值,将样本分成若干组,统计组内每个样本
k
k
kth距离内样本个数
N
x
N_x
Nx。
from sklearn.feature_selection import mutual_info_regression, mutual_info_classif
print(mutual_info_classif(X, y)) # 计算特征与离散目标的互信息值
print(mutual_info_regression(X, y)) # 计算特征与连续目标的互信息值
Information Value (IV) and Weight of Evidence (WOE)
对于二分类问题,可通过计算每个特征与目标之间的IV值筛选出重要特征。
定义:二分类问题有“正类”即“负类”,设正类样本总数
N
g
N_g
Ng, 负类样本总数
N
b
N_b
Nb。另假设根据特征Fea可将数据集分成k组,第
i
i
i组中含正类
N
g
i
N^i_g
Ngi,负类
N
b
i
N^i_b
Nbi,则:
D
G
i
=
N
g
i
N
g
D
B
i
=
N
b
i
N
b
W
O
E
i
=
l
n
(
D
G
i
D
B
i
)
I
V
=
∑
i
=
1
k
(
D
G
i
−
D
B
i
)
∗
W
O
E
i
DG^i = \frac{N^i_g}{N_g} \\ DB^i=\frac{N^i_b}{N_b} \\ WOE^i = ln(\frac{DG^i}{DB^i})\\ IV=\sum^k_{i=1}(DG^i-DB^i)*WOE^i
DGi=NgNgiDBi=NbNbiWOEi=ln(DBiDGi)IV=i=1∑k(DGi−DBi)∗WOEi
一般,可根据如下规则判定特征的重要性:
IV | 预测能力 |
---|---|
<0.02 | 几乎无用 |
0.02-0.1 | 较弱 |
0.1-0.3 | 中等 |
0.3-0.5 | 较强 |
0.5|无法判定,可能很强也可能很弱
def cal_iv(df, target, features=None):
# 传入DataFrame, 根据某个字段计算iv值
if features is None:
features = list(df.columns)
features.remove(target)
from collections import Counter
labels = Counter(df[target]) # 统计两类总数
b, g = labels.keys()
res_all = []
for fea in features:
split_points = df[fea].unique()
res = pd.DataFrame(columns=['db', 'dg'])
for i, p in enumerate(split_points):
tp = df[df[fea] == p]
tp_db = tp[tp[target] == b].shape[0] / labels[b]
tp_dg = tp[tp[target] == g].shape[0] / labels[g]
res.loc[i] = [tp_db, tp_dg]
res['woe'] = np.log(res['dg'] / res['db'])
res['iv'] = (res['dg'] - res['db']) * res['woe']
res_all.append(res.iv.sum())
ivs = pd.Series(res_all, index=features)
ivs.sort_values(ascending=False, inplace=True)
return ivs
递归特征消除(RFE)
选取一个估计器(例如GBDT),赋予每个特征权重,逐步从特征集合中删除权重最小的特征,直到特征集合中特征的数量达到目标值。示例如下:
from sklearn.feature_selection import RFECV
from sklearn.svm import SVC
svc = SVC()
rfe = RFECV(estimator=svc, step=1, min_features_to_select=3) # 根据svc赋予特征权重,每步删除权重最小的一个特征,最终保留3格特征
降维方法(dimension reduction)
将原始样本高维空间X映射到低维空间Y。
线性降维
可表示为
Y
=
W
T
X
Y=W^TX
Y=WTX
多维缩放(MDS):原始样本空间X有m个样本,特征维度为d0,变换后空间Y的特征维度为d1。在两个空间中,样本距离矩阵是一致的。由此计算得到,Y空间内样本的内积矩阵B与X空间一致,通过特征值分解B即得到Z空间内样本坐标。
主成分分析(PCA):映射到低维空间Z后,样本沿Z空间各个坐标轴的方差最大。
因子分析(FA):可以看作是主成分分析的推广,抽出的因子能更好地解释目标,因子往往具有明显的实际意义。该方法更加适合于特征存在异方差性。
线性判别分析(LDA):LDA适合于监督学习中数据的降维。核心思想就是挑选出能够很好解释类间方差的主成分,相比PCA充分考虑了类标记的信息。
from sklearn.decomposition import FactorAnalysis, PCA
pca = PCA(n_components=2)
fa = FactorAnalysis(n_components=2, max_iter=1000)
非线性降维
可表示为
Y
=
Φ
(
X
)
Y=\Phi(X)
Y=Φ(X)
核主成分分析(KPCA):通常运用核技巧(进行隐式非线性变化,因为在D和Z空间仅需要样本内积)的主成分分析方法。
流形学习(manifold learning)
流形就是连接在一起的区域。在机器学习领域,倾向于松散地定义为一组点,且只需要考虑少数嵌入在高维空间中的维度就能很好地近似。某点的维度指该点可局部变化方向的个数。例如一组点在高维空间种呈“8”字形分布,那么除了中间的交叉点维度是2之外,其他全是1。
t-SNE(t-Distributed Stochastic Neighbor Embedding)
t-SNE是最好的非线性降维算法之一,基本原理是将原始高维空间中的数据点之间的关系表示为联合概率
p
i
j
p_{ij}
pij,在低维空间中表示为
q
i
j
q_{ij}
qij,用KL散度(即损失函数)来衡量两个概率分布之间的距离。
p
i
j
p_{ij}
pij计算方法如下:
p
j
∣
i
=
e
−
∣
∣
x
i
−
x
j
∣
∣
2
/
2
σ
2
∑
k
=
i̸
e
−
∣
∣
x
i
−
x
k
∣
∣
2
/
2
σ
2
p
i
j
=
p
j
∣
i
+
p
i
∣
j
2
n
p_{j|i}=\frac{e^{-||x_i-x_j||^2/2\sigma^2}}{\sum_{k =\not i}e^{-||x_i-x_k||^2/2\sigma^2}} \\ p_{ij}=\frac{p_{j|i}+p_{i|j}}{2n}
pj∣i=∑k=ie−∣∣xi−xk∣∣2/2σ2e−∣∣xi−xj∣∣2/2σ2pij=2npj∣i+pi∣j式中,
σ
\sigma
σ需要根据困惑度(perplexity) 确定,该值决定了以某个样本为中心,对其起作用得近邻数,因此
σ
\sigma
σ是一个n_sample维向量。
q
i
j
q_{ij}
qij计算方法如下:
q
i
j
=
(
1
+
∣
∣
y
i
−
y
j
∣
∣
2
)
−
1
∑
k
=
l̸
(
1
+
∣
∣
y
k
−
y
l
∣
∣
2
)
−
1
q_{ij}=\frac{(1+||y_i-y_j||^2)^{-1}}{\sum_{k =\not l}(1+||y_k-y_l||^2)^{-1}}
qij=∑k=l(1+∣∣yk−yl∣∣2)−1(1+∣∣yi−yj∣∣2)−1
几点需要注意的地方(见参考资料6):
- perplexity:越大,算法越注重全局结构,故一般样本越多该值越大;过小的困惑度经常过度挖掘局部结构,导致没有意义的团簇;一般取值5-50之间。
- 当低维图像呈现"pinched"形状,建议延长迭代次数。
- 低维图像中团簇的大小是没有意义的,因为算法中的“距离”反映的是局部相对密度变化;同样团簇间的距离也没有重要参考意义,毕竟perplexity是一个全局参数。
- t-SNE是一个非常灵活的算法,建议多试验几个参数值。
等度量映射(Isomap)
Isomap可以视作MDS的一个延申。在高维空间中MDS直接根据点坐标计算点之间的距离有时不合理,例如计算位于曲面上两点距离。Isomap通过寻找两点之间的最短距离(例如Dijkstra算法),并以此作为两点之间的距离,从而构建样本距离矩阵,具体步骤有:
- 确定每个样本的最近邻样本点(方便寻找最短距离的下一个节点)
- 计算最短距离
- 采用MDS输出低维坐标
将训练样本高维坐标作为输入,低维坐标作为输出,构建非线性回归模型,则可将新样本的高维坐标转化为低维坐标。
局部线性嵌入(LLE)
故名思意,LLE尽量保持映射前后样本与其局部邻域内样本间距离不变。所谓"线性"是指在高维空间中将样本 x i x_i xi表示为邻域 Q i Q_i Qi内样本的线性组合 ∑ j ∈ Q i w i j x j \sum_{j \in Q_i}w_{ij}x_j ∑j∈Qiwijxj,则通过求解: m i n ∣ ∣ x i − ∑ j ∈ Q i w i j x j ∣ ∣ 2 2 s . t . ∑ w i j = 1 min\quad ||x_i-\sum_{j \in Q_i}w_{ij}x_j||_2^2 \\ s.t.\quad \sum w_{ij}=1 min∣∣xi−j∈Qi∑wijxj∣∣22s.t.∑wij=1即得系数矩阵 w i j w_{ij} wij,然后特征值分解 ( I − W ) T ( I − W ) (I-W)^T(I-W) (I−W)T(I−W),选取前 d ′ d' d′小个特征值对应特征向量,即为低维空间 Y T Y^T YT。
谱嵌入(Spectral Embedding)
聚类分析中,谱聚类将原始样本空间映射到拉普拉斯空间可以视为一种降维方法。
# skleran中流形学习算法示例
from sklearn.manifold import TSNE, Isomap, LocallyLinearEmbedding, SpectralEmbedding
from sklearn.datasets import load_digits
tsne = TSNE(n_components=2, # 低维空间维度
perplexity=30, # 困惑度
early_exaggeration=12, # 控制低维空间中团簇间的距离
learning_rate=100, # 学习率
n_iter=2000) # 迭代训练次数
isomap = Isomap(n_components=2, tol=1e-7)
lle = LocallyLinearEmbedding(n_neighbors=100, max_iter=5000, method='modified')
spe = SpectralEmbedding(n_neighbors=30)
if __name__ == '__main__':
X, _ = load_digits(return_X_y=True)
Y = tsne.fit_transform(X)
import matplotlib.pyplot as plt
plt.scatter(Y[:, 0], Y[:, 1])
plt.show()
自编码器降维
见另一篇博客《自编码器、变分自编码器(VAE)简介以及Python实现》。
参考资料
- 《机器学习》周志华
- Estimating mutual information
- Mutual Information between Discrete and Continuous Data Sets
- http://ucanalytics.com/blogs/information-value-and-weight-of-evidencebanking-case/
- Visualizing Data using t-SNE
- How to Use t-SNE Effectively
- sklearn官方指导文档
注:如有不当之处,请指正。