机器学习算法竞赛实战(二)

特征工程

“机器学习的本质上还是特征工程,数据和特征决定了机器学习的上限,而模型和算法知识逼近这个上限而已。”特征工程主要分为数据预处理,特征变换,特征提取,特征选择这四个部分。

数据预处理

数据质量直接决定了模型的准确性和泛化能力的高低,同时在构造特征时也会影响其顺畅性。

缺失值处理

  • 区分缺失值

    缺失值的表现出了None,NA和NaN这些,还包括用于表示数值缺失的特殊数值。在具体业务之中有些数值没有意义也应作缺失值处理。

  • 处理方法

    • 对于类别特征:填充众数,或者直接填充一个新类别
    • 对于数值特征:可以填充平均数,中位数,众数等
    • 对于有序数据:可以填充相邻值
    • 模型预测填充:可以对含有缺失值的那一列进行建模并预测其中缺失值的结果。

异常值处理

  • 寻找异常值

    • 可视化分析来寻找异常值
    • 通过简单的统计分析来发现异常值
  • 处理异常值

    • 删除含有异常值的记录
    • 视为缺失值
    • 平均值(中位数修正)
    • 不处理

优化内存

  • 内存回收机制,在我们删除一些变量时,使用gc.collect()来释放内存

  • 数值类型优化

    我们可以用np.iinfo来确认每一个int型子类型的最小值和最大值

    1
    2
    3
    import numpy as np
    np.iinfo(np.int8).min
    np.iinfo(np.int8).max

    此外,在不影响模型泛化能力的情况下,对于类别型的变量,若其编码ID的数字较大,极不连续而且种类较少,则可以重新从0开始编码,这样也能减少变量的内存占用,而对于数值型的变量,常常由于存在浮点数使得内存占用过多,可以考虑详见最小值和最大值归一化,然后乘以100,1000等,之后取整,这样不仅可以保留同一变量之间的大小关系,还极大地减少了内存占用。

特征变换

连续变量无量纲化

  • 标准化\(x'=\frac{x-\mu}{\sigma}\)
  • 区间缩放,常见的一种是利用两个最值进行缩放。

连续变量数据变换

  • log变换

    进行log变换可以将倾斜数据变得接近正态分布,这是因为大多数机器学习模型不能很好地处理非正态分布的数据。

    cbox-cox变换,自动寻找最佳正态分布变换函数地方法。

  • 连续变量离散化

    离散化后地特征对异常数据有很强地健壮性,更便于探索数据的相关性。

    • 无监督的离散化。分桶操作可以将连续变量离散化,同时使数据平滑,即降低噪声的影响,一般分为等频和等距两种分桶方式。
      • 等频。区间的边界值要经过选择,使得每个区间包含数量大致相等的变量实例。
      • 等距。将实例从最小值到最大值,均分为N等份,每份的距离是相等的。
    • 有监督的离散化。这类方法对目标有很好的区分能力,常用的是使用树模型返回叶子节点来进行离散化。

类别特征转换

  • 自然数编码。一列有意义的类别特征(即有顺序关系)可以用自然数进行编码,利用自然数的大小关系可以保留其顺序关系。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 调用sklearn中的函数
    from sklearn import preprocessing
    for f in columns:
    le = preprocessing.LabelEncoder()
    le.fit(data[f])


    # 自定义实现
    for f in columns:
    data[f] = data[f].fillna(-999)
    data[f] = data[f].map(dict(zip(data[f].unique(), range(0, data[f].nunique()))))
  • 独热编码。当前类别特征没有意义(即没有顺序关系)时,需要使用独热编码。

特征提取

类别相关的统计特征

  • 目标编码

    目标编码可以理解为用目标变量(标签)的统计量来对类别特征进行编码,即根据目标变量进行有监督的特征构造。目标编码可能对于基数较低的类别特征通常很有效,但对于基数较高的类别特征,可能会有过拟合的风险。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    folds = KFold(n_split=5, shuffle=True,random_state=2020)
    for col in columns:
    colname = col + '_kfold'
    for fold_, (trn_idx, val_idx) in enumerate(folds.split(train, train)):
    tmp = train.iloc[trn.idx]
    order_label = tmp.groupby([col])['label'].mean()
    train[colname] = train[col].map(order_label)
    order_label = train.groupby([col])['label'].mean()
    test[colname] = test[col].map(order_label)
  • count, nunique, ratio

  • 类别特征之间交叉组合

    交叉组合能够描述更细粒度的内容。对类别特征进行交叉组合在竞赛中是一项非常重要的工作,这样可以进行很好的非线性特征拟合。

数值相关的统计特征

  • 数值特征之间的交叉组合
  • 类别特征和数值特征之间的交叉组合
  • 按行统计相关特征

特征选择

特征关联性分析

特征关联性分析时使用统计量来为特征之间的相关性进行评分,特征按照分数进行排序,要么保留,要么从数据中删除。

  • 皮尔逊相关系数。这种方法不仅可以衡量变量之间的线性相关性,解决共线变量问题,还可以衡量特征与标签的相关性。共享变量是指变量之间存在高度相关关系,这会降低模型的学习可用性,可解释性以及测试集的泛化性能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 根据皮尔逊相关系数的计算提取top300的相似特征
    def feature_select_person(train, features):
    featureSelect = features[:]
    # 进行皮尔逊相关性计算
    corr = []
    for feat in featureSelect:
    corr.append(abs(train[[feat, 'target']].fillna(0).corr().values[0][1]))

    se = pd.Series(corr, index=featuresSelect).sort_values(ascending=False)
    feature_select = se[:300].index.toilst()
    return train[feature_select]
  • 卡方检验。它用于检验特征变量与因变量之间的相关性。\(\chi^2=\sum{\frac{(A-E)^2}{E}}\)

  • 互信息法。互信息是一个联合分布中两个变量之间相互影响关系的度量,也可以用来评价两个变量之间的相关性。

特征重要性分析

1
2
3
4
5
6
7
8
9
10
params = {
'max_depth': 10,
'subsample': 1,
'verbose': True,
'seed': 12,
'objective': 'binary:logistic'
}

xgtrain = xgb.DMartix(x, label=y)
bst = xgb.train(params, xgtrain, num_boost_round=10)
  • weight计算方式。计算特征在所有树中被选为分裂特征的次数

    1
    importtance = bst.get_score(fmap='', importance_type='weight')
  • gain计算方式。gain表示平均增益。

    1
    importtance = bst.get_score(fmap='', importance_type='gain')
  • cover计算方式。cover具体含义是特征对每棵树的覆盖率,即特征被分到该节点的样本的二阶导数之和。

    1
    importtance = bst.get_score(fmap='', importance_type='cover')

封装方法

封装方法是一个比较耗时的特征选择方法。可以将对一组特征的选择视作一个搜索问题,在这个问题中,通过准备,评估不同的组合并对这些组合进行比较,从而找出最优的特征子集。

  • 启发式方法

    • 前向搜索。每次增量的从剩余未选中的特征中选出一个并将其加入到特征集中。
    • 后向搜索。从特征全集开始,每次删除其中的一个特征并评价。
  • 递归消除特征法。递归消除特征法使用一个基模型来进行多轮训练,每轮模型都会先消除若干权值系数的特征在基于新特征集进行下一轮训练。

    1
    2
    3
    4
    5
    6
    from sklearn.feature_selection import RFE
    from sklearn.linear_model import LogisticRegression
    # 递归消除特征法,返回特征选择后的数据
    # 参数estimator为基模型
    # 参数n_features_to_select为选择的特征个数
    RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(data, target)

实战案例

数据预处理

执行下面的代码进行数据读取,删除缺失值比例大于50%的特征列,并对object型的缺失特征进行填充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test = pd.read_csv("test.csv")
train = pd.read_csv("train.csv")
ntrain = train.shape[0]
ntest = test.shape[0]

data = pd.concat([train, test], axis=0, sort=False)
# 删除缺失值比例大于50%的特征列
missing_cols = [c for c in data if data[c].isna().mean()*100 > 50]
data = data.drop(missing_cols, axis=1)

# 对object型的缺失特征进行填充
object_df = data.select_dtypes(include=['object'])
numerical_df = data.select_dtypes(exclude=['object'])

object_df = object_df.fillna('unknow')

对数值型特征用中位数进行填充

1
2
3
missing_cols = [c for c in numerical_df if numerical_df[c].isna().sum() > 0]
for c in missing_cols:
numerical_df[c] = numerical_df[c].fillna(numerical_df[c].median())

删除未选定的特征

1
object_df = object_df.drop(['Heating','RoofMatl','Condition2','Street','Utilities'],axis=1)

特征提取

  • 基础特征构造

    1
    2
    3
    4
    5
    6
    7
    numerical_df.loc[numerical_df['YrSold'] < numerical_df['YearBuilt'], 'YrSold' ] = 2009
    numerical_df['Age_House']= (numerical_df['YrSold'] - numerical_df['YearBuilt'])
    numerical_df['TotalBsmtBath'] = numerical_df['BsmtFullBath'] +
    numerical_df['BsmtHalfBath']*0.5
    numerical_df['TotalBath'] = numerical_df['FullBath'] + numerical_df['HalfBath']*0.5
    numerical_df['TotalSA'] = numerical_df['TotalBsmtSF'] + numerical_df['1stFlrSF'] +\
    numerical_df['2ndFlrSF']
  • 特征编码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    bin_map  = {'TA':2,'Gd':3, 'Fa':1,'Ex':4,'Po':1,'None':0,
    'Y':1,'N':0,'Reg':3,'IR1':2,'IR2':1, \
    'IR3':0,"None" : 0,"No" : 2, "Mn" : 2,
    "Av": 3,"Gd" : 4,"Unf" : 1, "LwQ": 2, \
    "Rec" : 3,"BLQ" : 4, "ALQ" : 5, "GLQ" : 6}
    object_df['ExterQual'] = object_df['ExterQual'].map(bin_map)
    object_df['ExterCond'] = object_df['ExterCond'].map(bin_map)
    object_df['BsmtCond'] = object_df['BsmtCond'].map(bin_map)
    object_df['BsmtQual'] = object_df['BsmtQual'].map(bin_map)
    object_df['HeatingQC'] = object_df['HeatingQC'].map(bin_map)
    object_df['KitchenQual'] = object_df['KitchenQual'].map(bin_map)
    object_df['FireplaceQu'] = object_df['FireplaceQu'].map(bin_map)
    object_df['GarageQual'] = object_df['GarageQual'].map(bin_map)
    object_df['GarageCond'] = object_df['GarageCond'].map(bin_map)
    object_df['CentralAir'] = object_df['CentralAir'].map(bin_map)
    object_df['LotShape'] = object_df['LotShape'].map(bin_map)
    object_df['BsmtExposure'] = object_df['BsmtExposure'].map(bin_map)
    object_df['BsmtFinType1'] = object_df['BsmtFinType1'].map(bin_map)
    object_df['BsmtFinType2'] = object_df['BsmtFinType2'].map(bin_map)

    PavedDrive = {"N" : 0, "P" : 1, "Y" : 2}

    object_df['PavedDrive'] = object_df['PavedDrive'].map(PavedDrive)
    # 选择剩余的object特征
    rest_object_columns = object_df.select_dtypes(include = ['object'])
    # 进行one-hot编码
    object_df = pd.get_dummies(object_df, columns = rest_object_columns.columns)

    data = pd.concat([object_df, numerical_df], axis=1, sort=False)

特征选择

1
2
3
4
5
6
7
8
9
10
11
12
13
def correlation(data, threshold):
col_corr = set()
corr_matrix = data.corr()
for i in range(len(corr_matrix.columns)):
for j in range(i):
if abs(corr_matrix.iloc[i, j]) > threshold: # 相似性分数与阈值对比
colname = corr_matrix.columns[i] # 获取列名
col_corr.add(colname)
return col_corr


all_cols = [c for c in data.columns if c not in ['SalePrice']]
corr_features = correlation(data[all_cols], 0.9)

机器学习算法竞赛实战(二)
https://www.spacezxy.top/2021/10/14/ML-Pratice/ML-Algorithm-pratice-2/
作者
Xavier ZXY
发布于
2021年10月14日
许可协议