最近在做天池项目过程中,涉及到最重要的一步骤就是特征工程。本文旨在总结特征工程知识点,项目实战请移步:特征工程详解及实战项目(2)
简单介绍特征工程
特征工程是将数据转换成更好表示潜在问题的特征,提高机器学习的性能。
所谓“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已”。
目录
- 数据预处理
1.1 缺失值处理
1.11 直接删除
1.12 填补法:统计值填补,插值法,预测值填充
1.2 异常值处理
1.21 箱型图判别
1.22 基于常识
1.3 去除唯一性
1.4 量钢化
1.41 归一化min-max
1.42 标准化z_score
1.5 连续数据离散化
1.51 二值化:设定阈值得到1或0
1.52 分桶:等宽法,等频法
1.6 类别数据处理
1.61 序号编码label
1.62 one-hot、哑变量get_dummies
- 数据预处理
- 特征构造
2.1 统计值构造:单变量、多变量构造新特征,验证相关性
2.2 函数变化法
- 特征构造
- 特征提取
3.1 过滤法
3.11 方差过滤法
3.12 相关系数
3.13 卡方检验
3.2 嵌入法
3.3 包装法
- 特征提取
- 类别不平衡
- 降维
5.1 无监督之PCA主成分分析
- 降维
- 交叉验证
1.数据预处理
1.1缺失值处理
1.1.1直接删除
如果某字段缺失值占比较大,对训练模型没有明显特征可言,可以选择直接删除该字段(只能删除训练集,慎用)
如果缺失值较少可以删除记录,但是会把其他字段不缺失也一并删除掉,所以不建议使用
python实现:df.dropna()
1.1.2 填补
- 统计值填补:用均值,中位数,众数等。或者可以用空值、自定义数值、向前向后填补。当特征具有强类别信息时,比如男女性别身高填补,要分类统计效果才会好。
注:由于众数可能存在多个,要考虑到这点
注:向前后填补要考虑到第/最后一行为空值情况
df.fillna(method=' ')
- 插值法填补:在X个范围内求出一个值填至缺失值中
def f(s,n,k=5):#s=df,n缺失值位置,k范围=5
y = s[list(range(n-k,n+1+k))]#y求出缺失值前5后5的df数值
y = y[y.notnull()]#确保里面没有nan
return(lagrange(y.index,list(y))(n))#lagrange计算出第n个值
for i in range(len(df)):
if df.isnull()[i]:
data[i] = f(df,i)
print(f(df,i))
1.1.3预测值填充
a.把需要填充的某列作为新特征列Label_A
b.选出与Label_A相关性强的组成模型
c.非缺失值作为训练集,缺失值作为测试集
d.连续性数据用回归预测,离散型进行分类学习
dataset['Label_A']=dataset['A']#a
dataset.corr()#b
x_trian,y_train,x_test,y_test = train_test_split(x,y)#c,x是b中相关性较强的特征列,y是结果列
lr = LinearRegression()
lr.fit(x_train, y_train)
y_train_pred = lr.predict(x_train)
print('>>>在训练集中的表现:', r2_score(y_train_pred, y_train))
y_valid_pred = lr.predict(x_valid)
print('>>>在验证集中的表现:', r2_score(y_valid_pred, y_valid))
y_test_pred = lr.predict(test.iloc[:, :2])
test.loc[:, 'Label_petal_length'] = y_test_pred
df_no_nan = pd.concat([train, test], axis=0)
1.2异常值处理
- 基于常识,判定为异常值,比如:年龄不能有负数
- 箱型图判断异常值
IQR=Q3-Q1
极端异常值上限>Q3+3IQR
极端异常值下限<Q1-3IQR
一般异常值上限>Q3+1.5IQR
一般异常值下限<Q1-1.5IQR - 3西格玛方法:
如果数据符合正态分布,那么缺失值被定义为值与均值之差的绝对值大于3倍标准差
异常值处理方法一般是删除或填补
1.3去除唯一属性
一般都是ID类属性不能反映样本特征情况,直接删除即可
1.4 无量纲化
- 归一化min-max
x* = (x-x.min)/(x.max-x.min)
又叫区间缩放法,把值缩放至[0,1]区间
from sklearn.preprocessing import MinMaxScaler
x_scaler = MinMaxScaler()
y_scaler = MinMaxScaler()
#特征归一化
x_train_sca = x_scaler.fit_transform(X_train)
x_test_sca = x_scaler.transform(X_test)
y_train_sca = y_scaler.fit_transform(pd.DataFrame(y_train))
- 标准化z_score
x* = (x-x.mean)/σ
from sklearn.preprocessing import StandardScaler
#一般把train和test集放在一起做标准化,或者在train集上做标准化后,用同样的标准化器去标准化test集
scaler = StandardScaler()
train = scaler.fit_transform(train)
test = scaler.transform(test)
1.5 连续变量离散化
需要离散化的原因:
- 算法需要,如决策树,朴素贝叶斯是基于离散型数据
- 离散化数据较连续型更容易被理解
- 强鲁棒性,客服数据缺陷
- 离散化后数据稳定性强,可以进行特征交叉,对于逻辑回归来说离散后每个特征具有权重,相当于为模型引入非线性
二值化:设定阈值,大于阈值为1,反之为0
from sklearn.preprocessing import Binarizer
# 阈值自定义为 3.0
# 大于阈值映射为 1,反之为 0
b = Binarizer(threshold=3.0)
x_binarizer = b.fit_transform(df.iloc[:, :4])
x_binarizer[:5]
分桶
*等宽:按照特征值划分pd.cut
*等频:将样本数量等频数划分pd.qcut
聚类划分
KMeans无监督学习,定义k个簇不断增加样本值改变簇的位置,直到簇不再改变。
# 聚类划分
import seaborn as sns
import numpy as np
from sklearn.cluster import KMeans
data = sns.load_dataset('iris')
X = data.iloc[:,1]
kmeans = KMeans(n_clusters=4) # 离散为 4 等份
kmeans.fit_transform(np.array(X).reshape(-1, 1)) # 只取一个特征进行聚类离散化
1.6 类别数据处理
- 序号编码,可以理解为打标签
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
x_le = le.fit_transform(data['species'])
x_le
- One-hot编码
from sklearn import preprocessing
x = np.array([[1,2,3],[3,4,6],[1,5,6]])
l = preprocessing.OneHotEncoder()
l.fit(x)
y = np.array([3,2,6])
print(l.transform(y.reshape(1,-1)).toarray())
result: [[0. 1. 1. 0. 0. 0. 1.]]
注:可能会造成特征灾害,需要用特征选择降维
LR模型一般会用到连续数据离散化,再进行one-hot编码,使其具有非线性能力
one-hot与哑变量的区别
2. 特征构造
属性分割和结合是特征构造中常用的方法,可以尝试组合二个、三个不同的属性构造新的特征。如果存在时间相关性,可以得到同一属性下不同时间的特征。
2.1 统计值构造
比如:统计单个变量次数作为新特征,多变量统计值作为新特征
# 多变量
name = {'count': 'petal_width_count', 'min':'petal_width_min',
'max':'petal_width_max', 'mean':'petal_width_mean',
'std':'petal_width_std'}
newF2 = df.groupby(by=['sepal_length'])['petal_width'].agg(['count', 'min', 'max', 'mean', 'std']).rename(columns=name).reset_index()
df_newF2 = pd.merge(df, newF2, on='sepal_length', how='inner')
# 由于聚合分组之后有一些样本的 std 会存在缺失值,所以统一填充为 0
df_newF2['petal_width_std'] = df_newF2['petal_width_std'].fillna(0)
df_newF2.head()
验证相关性:
df_newF2.corr()
通过验证相关系数表可以发现,新构建的 5 个特征,除了 count、std 之外,其余 3 个特征跟目标相关性系数较高,
可以初步认为这 3 个特征是有实际意义的特征,下面进行模型训练简单验证一下。
2.3 函数变化法
包括:平方(小数值-大数值)、开方(大数值-小数值)、对数(非正态-正态分布)、指数、差分(常用于时间序列)
2.31 对数法
2.32 差分法
plt.rcParams['font.sans-serif'] = ['SimHei']#避免中文乱码
plt.rcParams['axes.unicode_minus'] = False#避免中文乱码
y = np.array([1.3, 5.5, 3.3, 5.3, 3.4, 8.0, 6.6, 8.7, 6.8, 7.9])
x = np.array(list(range(y.shape[0])))
plt.figure(1,figsize = (5,3)),plt.title('原数据')
plt.plot(x,y)
#print('----log----')
y_log = np.log(y)
plt.figure(2,figsize = (5,3)),plt.title('log变换后数据')
plt.plot(x,y_log)
#print('----差分---')
def diff(data):
DIFF = []
DIFF.append(data[0])
for i in range(1,data.shape[0]):# 1 次差分
value = data[i]-data[i-1]
DIFF.append(value)
for i in range(1,data.shape[0]):# 2 次差分
value = DIFF[i]-DIFF[i-1]
DIFF.append(value)
x = list(range(1,len(DIFF)+1))
plt.figure(3,figsize = (5,3)),plt.title('2次差分后数据')
plt.plot(x,DIFF)
return DIFF
DIFF = diff(y)
从上面图像发现经过对数变换的数据明显比差分变换的效果更好,对数变换后的数据更加的平稳。
2.33 算术运算法
如featureA/featureB
2.4 连续数据离散化
见预处理部分
2.5 离散数据编码
见预处理部分
3. 特征提取
选取对模型预测重要性的特征,减少模型预测时间提高精确度。
3.1 过滤法Filter
单个特征与目标特征的相关性。适用于离散型变量。
3.11 方差过滤法
方差是与偏离均值的程度大小,方差越大离散程度越大,意味着该变量对模型贡献作用越明显,应该保留。
# 调用 sklearn 模块 API 接口
from sklearn.feature_selection import VarianceThreshold
vt = VarianceThreshold(0.6)#设定阈值,默认为0
x_vt = vt.fit_transform(df.iloc[:,:4])
print(vt.variances_)#查看所有方差
x_vt[:5]
3.12 相关系数
特征与特征之间的相关系数
corr<0,代表负相关,互补特征;
corr=0,代表无相关;
corr>0,代表正相关,替代特征;
corr的绝对值在0.9-1之间,就可以保留其中一个特征即可。目标与特征之间的相关系数及p值
适用于回归问题的特征选择。先检验显著性,如果显著再看相关系数。
# 调用 sklearn 模块 API 接口
from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
from numpy import array
fun = lambda X, Y: tuple(map(tuple, array(list(map(lambda x: pearsonr(x, Y), X.T))).T))
sb = SelectKBest(fun, k=2)#返回k个最佳特征
x_sb = sb.fit_transform(df.iloc[:,:4], df.iloc[:, 4])
print('>>>检验统计值(相关系数):\n', sb.scores_)
print('\n>>>P值:\n', sb.pvalues_)
x_sb[:5]
3.13 卡方检验
卡方检验是比较理论频数和实际频数的吻合程度,适用于分类问题
就是统计样本的实际观测值与理论推断值之间的偏离程度,实际观测值与理论推断值之间的偏离程度就决定卡方值的大小**(对于分类问题(y离散)),卡方值越大,越不符合;卡方值越小,偏差越小,越趋于符合
# 调用 sklearn 模块 API 接口
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
skb = SelectKBest(chi2, k=2)
x_skb = skb.fit_transform(df.iloc[:,:4], df.iloc[:,4])
print('>>>检验统计值(卡方值):\n', skb.scores_)
print('\n>>>P值:\n', skb.pvalues_)
x_skb[:5]
3.2 嵌入法Embedde
嵌入法是特征和算法同时进行,让算法决定使用哪种特征。
先使用算法和模型进行训练,得到各个特征的权重系数由大到小,这个系数代表特征对模型的重要性。比如决策树和树的集成模型中的feature_importances_属性,可以列出各个特征对树的建立的贡献,我们就可以基于这种贡献的评估,找出对模型建立最有用的特征。
因此相比于过滤法,嵌入法会精确到模型本身。但是过滤法会通过统计常识查找范围,比如p值0.05,而嵌入法的权值系数最佳临界值需要判断。
3.3 包装法Wrapper
未完待续
4. 类别不平衡
未完待续
5. 降维
5.1 无监督主成成分PCA
通过线性变换将原始数据变换为一组各维度线性无关的表示,用于提取主要特征。是一个过程结果,用于辅助模型。
如上图所示,将100个维度降至10个维度,10个维度中每个维度=特征值b1*新变量a1,而新变量a1又是通过原100个维度得来的,无法形容a1到底是什么,但却与100个维度都相关。其中b就是特征向量,将b从大到小排序,计算累计贡献率,一般选取贡献率达到85%以上的为主成分。
from sklearn.datasets import load_digits
digit = load_digits()
pca = PCA(n_components=10)
pca.fit(digit['data'])
print(pca.explained_variance_)#特征值b
print(pca.components_)#特征向量e
s = pca.explained_variance_
df = pd.DataFrame({'z':s,
'z_sum':s.cumsum()/s.sum()})
df['z_sum'].plot(linestyle = '--',marker = 'o')
plt.axhline(0.85)
6. 交叉验证
交叉验证就是将样本数据分位训练集和测试集,训练集用来训练模型测试集用来评估模型预测好坏。得到多组训练集和测试集,这次是训练集下次可能会成为测试集,这就是所谓的“交叉”。
一般在样本数量级少于1w条时,会用到交叉验证。当样本集较大时,会将数据集分为三份,训练集用来训练模型,验证集用来验证模型调参,把最终得到的模型再用于测试集,最终决定使用哪个模型以及对应参数。
主要介绍简单交叉验证、k折交叉验证和分层交叉验证
6.1 简单交叉验证train_test_split
随机将样本集分为训练集和测试集
from sklearn.model_selection import train_test_split
'''
(1)random_state不填或者为0时,每次都不同;其余值表示不同随机数
(2)shuffle表示是否在分割之前对数据进行洗牌(默认True)
'''
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.30,random_state=42,shuffle=True)
测试集占30%训练集70%.
这种方法处理简单,缺点是没有达到交叉的思想。
6.2 k折交叉验证KFold
将数据集均分k分,其中k-1份作为训练集,1分作为测试集。重复k次后,将所得出模型准确率的均值作为当前 k 折交叉验证下模型的性能指标。
from sklearn.model_selection import KFold
kf = KFold(n_splits=2)
for train_index, test_index in kf.split(X):
print('X_train:%s ' % X[train_index])
print('X_test: %s ' % X[test_index])
6.3 分层交叉验证StratifiedShuffleSplit
对于非平衡数据选择分层交叉
from sklearn.model_selection import StratifiedKFold,StratifiedShuffleSplit # 分层分割
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1,2]])
Y = np.array([0, 0, 0, 1, 1])
ss = StratifiedShuffleSplit(n_splits=5, test_size=0.5, random_state=seed)
for train_index, test_index in ss.split(X, Y):
print(train_index, test_index)
结果:
[4 0] [1 2 3]
[1 4] [0 2 3]
[3 1] [0 2 4]
[2 3] [1 4 0]
[3 0] [4 1 2]
举个简单的例子:
n_splits分成多少组
test_size测试集占比多少
这里数据集为5行3列,重复5组
(1)test_size占比0.5,那么5组0.5=2.5,四舍五入=3,那么测试集在5行中占3行,训练集就占2行。
(2)看y值中包括3个负样本2个正样本,那么负样本占比3/5,正样本占比2/5
(3)那么训练样本2个中负样本有23/5=1.2约等于1个,正样本有22/5=0.8约等于1个;测试样本3个中负样本有33/5=1.8约等于2个,正样本有3*2/5=1.2约等于1个;
参考:
http://www.elecfans.com/d/816121.html连续变量离散化
https://blog.csdn.net/snowdroptulip/article/details/78770088卡方检验
https://blog.csdn.net/weixin_43172660/article/details/84340164特征选择