[笔记整理] Task3-特征工程

特征工程

    • 定义
    • 常见的特征工程
      • 异常处理
        • 箱线图(或3-Sigma)分析
      • 特征归一化/标准化
      • 数据分桶
      • 缺失值处理
      • 特征构造
      • 特征筛选
      • 降维

定义

特征工程 (Feature engineering):

  • (From Wikipedia) the process of using domain knowledge to extract features from raw data via data mining techniques. These features can be used to improve the performance of machine learning algorithms. Feature engineering can be considered as applied machine learning itself.

常见的特征工程

异常处理

箱线图(或3-Sigma)分析

通过箱线图(或 3-Sigma)分析删除异常值
Notes:

  • 四分位距(IQR)= Q3 - Q1
  • 上限 ---- 非异常范围内的最大值。上限=Q3 +1.5 * IQR
  • 下限 ---- 非异常范围内的最小值。下限=Q1 -1.5* IQR
  • 为什么 箱线图 可以识别异常值
    • 线图判断异常值的标准以四分位数和四分位距为基础,
    • 四分位数具有一定的耐抗性,多达25%的数据可以变得任意远不会很大地扰动四分位数,所以异常值不会影响箱形图的数据形状,箱线图识别异常值的结果比较客观。
    • 由此可见,箱线图在识别异常值方面有一定的优越性
  • 异常值越多说明尾部越重,自由度越小(即自由变动的量的个数);
  • 而偏态表示偏离程度,异常值集中在较小值一侧,则分布呈左偏态;异常值集中在较大值一侧,则分布呈右偏态。
    以下代码转载 知乎:阿泽 https://www.zhihu.com/people/is-aze
# 这里我包装了一个异常值处理的代码,可以随便调用。
def outliers_proc(data,col_name,scale = 3):
    """
    用于清洗异常值,默认用 box_plot(scale=3)进行清洗
    :param data: 接收 pandas 数据格式
    :param col_name: pandas 列名
    :param scale: 尺度
    :return:
    """
    def box_plot_outliers(data_ser,box_scale):
        """
        利用箱线图去除异常值
        :param data_ser: 接收 pandas.Series 数据格式
        :param box_scale: 箱线图尺度,
        :return:
        """
        iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25))
        val_low = data_ser.quantile(0.25) - iqr
        val_up = data_ser.quantile(0.75) + iqr 
        rule_low = (data_ser < val_low)
        rule_up = (data_ser > val_up)
        return (rule_low, rule_up),(val_low,val_up)

    data_n = data.copy()
    data_series = data_n[col_name]
    rule,value = box_plot_outliers(data_series,box_scale = scale)
    index = np.arange(data_series.shape[0])[rule[0]|rule[1]]
    print("Delete number is: {}".format(len(index)))

    data_n = data_n.drop(index)
    data_n.reset_index(drop = True, inplace=True)
    print("Now column number is: {}".format(data_n.shape[0]))
    index_low = np.arange(data_series.shape[0])[rule[0]]
    outliers = data_series.iloc[index_low]
    print("Description of data less than the lower bound is:")
    print(pd.Series(outliers).describe())
    index_up = np.arange(data_series.shape[0])[rule[1]]
    outliers = data_series.iloc[index_up]
    print("Description of data larger than the upper bound is:")
    print(pd.Series(outliers).describe())

    fig, ax = plt.subplots(1, 2, figsize=(10, 7))
    sns.boxplot(y=data[col_name], data=data, palette="Set1", ax=ax[0])
    sns.boxplot(y=data_n[col_name], data=data_n, palette="Set1", ax=ax[1])
    return data_n
  1. BOX-COX 转换(处理有偏分布)

  2. 长尾截断

特征归一化/标准化

  1. 标准化
  2. 归一化
    Notes:
  • 归一化:把数变为(0,1)之间的小数
    - 主要是为了数据处理方便提出来的,把数据映射到0~1范围之内处理,更加便捷快速,应该归到数字信号处理范畴之内。(转载 http://blog.csdn.net/pipisorry/article/details/52247379)

  • 好处1. 提升模型的收敛速度

    • 如下图,x1的取值为0-2000,而x2的取值为1-5,假如只有这两个特征,对其进行优化时,会得到一个窄长的椭圆形,导致在梯度下降时,梯度的方向为垂直等高线的方向而走之字形路线,这样会使迭代很慢,相比之下,右图的迭代就会很快(理解:也就是步长走多走少方向总是对的,不会走偏)
      [笔记整理] Task3-特征工程_第1张图片
  • 好处2.提升模型的精度

    • 归一化的另一好处是提高精度,这在涉及到一些距离计算的算法时效果显著,比如算法要计算欧氏距离,上图中x2的取值范围比较小,涉及到距离计算时其对结果的影响远比x1带来的小,所以这就会造成精度的损失。
    • 所以归一化很有必要,他可以让各个特征对结果做出的贡献相同。
  • 深度学习中数据归一化可以防止模型梯度爆炸。

  • 数据需要归一化的机器学习算法

    • 有些模型在各个维度进行不均匀伸缩后,最优解与原来不等价,
      • 例如 SVM(距离分界面远的也拉近了,支持向量变多?)。对于这样的模型,除非本来各维数据的分布范围就比较接近,否则必须进行标准化,以免模型参数被分布范围较大或较小的数据dominate。
    • 有些模型在各个维度进行不均匀伸缩后,最优解与原来等价,
      • 例如 logistic regression(因为θ的大小本来就自学习出不同的feature的重要性吧?)。
      • 对于这样的模型,是否标准化理论上不会改变最优解。但是,由于实际求解往往使用迭代算法,如果目标函数的形状太“扁”,迭代算法可能收敛得很慢甚至不收敛(模型结果不精确)。
      • 所以对于具有伸缩不变性的模型,最好也进行数据标准化。
    • 有些模型/优化方法的效果会强烈地依赖于特征是否归一化,
      • LogisticReg,SVM,NeuralNetwork,SGD,PCA 降维
        • PCA将原来高维的数据投影到某个低维的空间上并使得其方差尽量大。如果数据其中某一特征数值特别大,那么它在整个误差计算的比重上就很大,那么可以想象在投影到低维空间之后,为了使低秩分解逼近原数据,整个投影会去努力逼近最大的那一个特征,而忽略数值比较小的特征,这很可能导致了大量的信息缺失
        • 此外,从计算的角度讲,因为 PCA 通常是数值近似分解,而非求特征值、奇异值得到解析解,所以当我们使用梯度下降等算法进行PCA的时候,归一化有利于梯度下降收敛]等。
  • 不需要归一化的模型:
    -(0/1取值的特征通常不需要归一化,归一化会破坏它的稀疏性。)

    • 有些模型则不受归一化影响,如DecisionTree。
    • ICA好像不需要归一化(因为独立成分如果归一化了就不独立了?)。
    • 基于平方损失的最小二乘法 OLS 不需要归一化。
  • 常见归一化方法:

    • 最常用的是 min-max标准化 和 z-score 标准化
    • min - max: x − m i n m a x − m i n \frac {x-min} {max - min} maxminxmin
      • 这种方法有一个缺陷就是当有新数据加入时,可能导致max和min的变化,需要重新定义
    • z-score: 也叫标准差标准化,这种方法给予原始数据的均值(mean)和标准差(standard deviation)进行数据的标准化。
      • 经过处理的数据符合标准正态分布,即均值为0,标准差为1,注意,一般来说z-score不是归一化,而是标准化,归一化只是标准化的一种。
      • x − μ σ \frac{x-\mu} {\sigma} σxμ
from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
data['power'] = np.log(data['power'] + 1)
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
data['power'].plot.hist()
  1. 针对幂律分布,可以采用公式: l o g ( 1 + x 1 + m e d i a n ) log(\frac{1+x}{1+median}) log(1+median1+x)

数据分桶

  1. 等频分桶
  2. 等距分桶
  3. Best-KS 分桶(类似利用基尼指数进行二分类)
  4. 卡方分桶

Notes:

  • 数据分桶/箱:是一种将多个连续值分组为较少数量的“分箱”的方法
  • 分箱的数据不一定必须是数字,它们可以是任何类型的值,如“狗”,“猫”,“仓鼠”等。 分箱也用于图像处理,通过将相邻像素组合成单个像素,它可用于减少数据量。
  • 好处:一般在建立分类模型时,需要对连续变量离散化,特征离散化后,模型会更稳定,降低了模型过拟合的风险
    • (转载:https://www.jianshu.com/p/0805f185ecdf)
    • 离散特征的增加和减少都很容易,易于模型的快速迭代;
    • 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
    • 离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。4. 如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
    • 逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单6. 独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;
    • 离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
    • 特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
    • 特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
    • 可以将缺失作为独立的一类带入模型。
    • 将所有变量变换到相似的尺度上
bin = [i*10 for i in range(31)]
data['power_bin'] = pd.cut(data['power'], bin, labels=False)
data[['power_bin', 'power']].head()
# 我们刚刚已经对 train 进行异常值处理了,但是现在还有这么奇怪的分布是因为 test 中的 power 异常值,
# 所以我们其实刚刚 train 中的 power 异常值不删为好,可以用长尾分布截断来代替
# 我们对其取 log,再做归一化
from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
data['power'] = np.log(data['power'] + 1)
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
data['power'].plot.hist()

缺失值处理

  1. 不处理 (针对类似 XGBoost 等树模型)
    Notes
  • errors : {‘ignore’, ‘raise’, ‘coerce’}, default ‘raise’
    • If ‘raise’, then invalid parsing will raise an exception (唤起异常)
    • If ‘coerce’, then invalid parsing will be set as NaT
    • If ‘ignore’, then invalid parsing will return the input
# 训练集和测试集放在一起,方便构造特征
train['train'] = 1 # add a new column named 'train'
test['train'] = 0
data = pd.concat([train,test],ignore_index = True, sort = False)

# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比
# 不过要注意,数据里有时间出错的格式,所以我们需要 errors='coerce'
data['used_time'] = (pd.to_datetime(data['creatDate'],format = '%Y%m%d', errors = 'coerce') -
                    pd.to_datetime(data['regDate'],format = '%Y%m%d',errors = 'coerce'))
data['used_time'].isnull().sum() # 15072
# 看一下空数据,有 15k 个样本的时间是有问题的,我们可以选择删除,也可以选择放着。
# 但是这里不建议删除,因为删除缺失数据占总样本量过大,7.5%                    
  1. 删除 (缺失数据太多)
  2. 插值补全
    • 包括均值/ 中位数/ 众数/ 建模预测/ 多重插补/ 压缩感知补全/ 矩阵补全等
  3. 分箱,缺失值一个箱

特征构造

  1. 构造统计量特征、报告计数、求和、比例、标准差等

Example:

# 计算某品牌的销售统计量,同学们还可以计算其他特征的统计量
# 这里要以 train 的数据计算统计量
train_gb = train.groupby("brand")
all_info = {
     }
for kind, kind_data in train_gb:
    info = {
     }
    kind_data = kind_data[kind_data['price'] > 0]
    info['brand_amount'] = len(kind_data)
    info['brand_price_max'] = kind_data.price.max()
    info['brand_price_median'] = kind_data.price.median()
    info['brand_price_min'] = kind_data.price.min()
    info['brand_price_sum'] = kind_data.price.sum()
    info['brand_price_std'] = kind_data.price.std()
    info['brand_price_average'] = round(kind_data.price.sum() / (len(kind_data) + 1), 2)
    all_info[kind] = info
brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={
     "index": "brand"})
data = data.merge(brand_fe, how='left', on='brand')
  1. 时间特征,包括相对时间和绝对时间,节假日,双休日等;
    Notes
  • errors : {‘ignore’, ‘raise’, ‘coerce’}, default ‘raise’
    • If ‘raise’, then invalid parsing will raise an exception (唤起异常)
    • If ‘coerce’, then invalid parsing will be set as NaT
    • If ‘ignore’, then invalid parsing will return the input

Example:

# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比
# 不过要注意,数据里有时间出错的格式,所以我们需要 errors='coerce'
data['used_time'] = (pd.to_datetime(data['creatDate'],format = '%Y%m%d', errors = 'coerce') -
                    pd.to_datetime(data['regDate'],format = '%Y%m%d',errors = 'coerce'))
  1. 地理信息,包括分箱,分布编码等方法;
    Example:
# 从邮编中提取城市信息,因为是德国的数据,所以参考德国的邮编,相当于加入了先验知识
data['city'] = data['regionCode'].apply(lambda x : str(x)[:-3])
  1. 非线性变换,包括log / 平方 / 根号等;

Example:

data['power'] = np.log(data['power'] + 1)
# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比
# 不过要注意,数据里有时间出错的格式,所以我们需要 errors='coerce'
data['used_time'] = (pd.to_datetime(data['creatDate'],format = '%Y%m%d', errors = 'coerce') -
                    pd.to_datetime(data['regDate'],format = '%Y%m%d',errors = 'coerce'))
  1. 特征组合;特征交叉;

Notes

  • errors : {‘ignore’, ‘raise’, ‘coerce’}, default ‘raise’
    • If ‘raise’, then invalid parsing will raise an exception (唤起异常)
    • If ‘coerce’, then invalid parsing will be set as NaT
    • If ‘ignore’, then invalid parsing will return the input
  • 分开构造特征
    • 是因为,不同模型对数据集的要求不同

Example: 构造类别特征

# 对类别特征进行 OneEncoder
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',
'gearbox', 'notRepairedDamage', 'power_bin'])

特征筛选

  1. 过滤式(filter):先对数据进行特征选择,然后再训练学习器,
    • 常见的方法有Relief / 方差选择法 / 相关系数法 / 卡方检验法 / 互信息法
      Example:相关性分析—热力图
data_numeric = data[['power', 'kilometer', 'brand_amount', 'brand_price_average',
'brand_price_max', 'brand_price_median']]
correlation = data_numeric.corr()
f , ax = plt.subplots(figsize = (7, 7))
plt.title('Correlation of Numeric Features with Price',y=1,size=16)
sns.heatmap(correlation,square = True, vmax=0.8)
  1. 包裹式(wrapper):直接把最终将要使用的学习器的性能作为特征子集的评价准则
    • 常见方法: LVM(Las Vegas Wrapper)
# k_feature 太大会很难跑,没服务器,所以提前 interrupt 了
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.linear_model import LinearRegression
sfs = SFS(LinearRegression(),
          k_features=10,
          forward=True,
          floating=False,
          scoring = 'r2',
          cv = 0)
x = data.drop(['price'], axis=1)
cols = [i for i in x.columns if i not in ['used_time']]
# x['used_time'] = x['used_time'].fillna(pd.Timedelta(seconds=0))
x['used_time'] = x['used_time'].fillna(pd.Timedelta('0 days'))
x = x[cols].fillna(0)
y = data['price']
sfs.fit(x, y)
sfs.k_feature_names_
  1. 嵌入式(embedding):结合过滤式和包裹式,学习器训练过程中自动进行了特征选择
    • 常见方法有 Lasso 回归

降维

  1. PCA / LDA / ICA
  2. 特征选择也是一种降维

你可能感兴趣的:(机器学习,python)