机器学习 特征工程【汇总1】

目录

  1. 需要哪些数据
  2. 数据如何存储
  3. 数据如何清洗
  4. 数据特征工程

1. 需要哪些数据

  1. 在进行机器学习之前,存在一个收集数据的过程,我们主要按照以下规则找出我们所需要的数据:
    1. 业务的实现需要哪些规则?
      1. 基于对业务规则的理解,尽可能找出对因变量有影响的所有自变量的数据
      2. 数据埋点的流程:提需求—>前端植入埋点代码块—>灰度测试—>测试验收—>发版上线
    2. 数据的可用性评估
      1. 在获取数据的过程中,首先要考虑这个数据获取的成本;
      2. 获取得到的数据,在使用之前。需要考虑一下这个数据是否覆盖了所有的情况以及这个数据的可信度情况。
    3. 获取数据的种类
      1. 用户的行为数据:记录在用户的系统上所有操作所留下来的日志行为数据
      2. 业务数据:商品/物品的信息、用户、会员的信息…
      3. 第三方数据:爬虫数据,购买的数据、合作方的数据

2. 数据如何存储

  1. 一般情况下,用于后期模型创建的数据都是存在本地磁盘、关系型数据库或者一些相关的分布式数据存储平台。
    1. 本地磁盘
    2. MySql
    3. Oracel
    4. HBase
    5. HDFS
    6. Hive

3. 数据清洗

  1. 数据清洗(Data Cleaning)是机器学习过程中不可缺少的一个环节,数据的清洗结果直接关系到模型的效果和最终结论。在实际工作中,数据清洗通常占开发过程的30%-50%左右的时间。
  2. 数据清洗的过程
    在这里插入图片描述

3.1 数据预处理

  1. 预处理过程主要考虑两个方面:
    1. 选择数据数据处理工具:关系型数据库或者Python
    2. 查看数据的元数据以及数据特征:一是查看元数据,包括字段解释、数据来源等一切可以描述数据的信息(df.info());另外是抽取一部分数据,通过人工查看的方式,对数据本身做一个比较直观的了解,并且初步发现一些问题,为之后的数据处理做准备。

3.2 清洗异常样本

3.2.1 格式内容错误数据清洗

  1. 一般情况下,数据是由用户/访客产生的,也就有很大可能性存在内容或者格式上不一致的情况,所以在进行模型构建之前需要先进行数据的格式内容清洗操作。格式内容问题主要有以下几类:
    1. 时间、日期、数值、半全角等显示格式不一致:直接将数据转换为一类格式即可,该问题一般出现在多个数据源整合的情况下。
    2. 内容中又不该存在的字符:最典型的就是在头部、中间、尾部空格等问题,这种情况下,需要以半自动校验加人工方式来找出来,并去除不需要的字符。
    3. 内容与该字段应有的内容不符:比如姓名写成了性别、身份证写成了手机号码等问题。

3.2.2 逻辑错误清洗

  1. 主要是通过简单的逻辑推理发现数据中的问题数据,防止分析结果走偏,主要包括以下几个步骤:
    1. 数据去重(drop_duplicates);
    2. 去重/替换不合理的值;
    3. 去除/重构不可靠的字段值(修改矛盾的内容)

3.2.3 去除不需要的数据

  1. 一般情况下,我们会尽可能多的手机数据,但是不是所有的字段数据都可以应用到模型构过程中,也就是说所有的字段属性都放到构建模型中,最终模型的效果就一定好。实际上来讲,字段属性越多,模型的构建越慢,所以有时候可以考虑将不要的字段进行删除操作。在进行该过程的时候,要注意备份原始数据。

3.2.4 关联性验证

如果数据源有多个,那么有必要进行关联性验证。该过程经常用到多个数据源合并的过程中,通过验证数据之间的关联性选择比较正确的特征属性。比如:汽车的线下购买信息和电话客服问卷信息,两者之间可以通过姓名和手机号进行关联操作,匹配两者之间的车辆信息是否是同一辆,如果不是,如果不是,那么就需要进行数据调整。

数据不平衡

在实际应用中,数据往往分布的非常不均匀,也就是会出现“长尾现象”,即绝大多数的数据在一个范围或者属于某一个类别,而在另外一个范围或者另外一个类别中只有一部分数据。那么这个时候直接使用机器学习可能效果不会太好。因此这个时候需要进行一系列转换操作:
在这里插入图片描述
就算直接判断错误,效果也会很好。因为损失函数中的负样本损失非常大,到时模型在不断迭代优化时,负样本loss 更小的方向变化,这样就使得模型预测负样本越来越准确,而忽略了正样本的预测情况。少数样本判断正确与否,都不会引起损失函数较大的变化。

import numpy as np
from sklearn.datasets import make_gaussian_quantiles
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score, accuracy_score
from matplotlib import pyplot as plt
import seaborn  as sns
import warnings
from  prettytable import PrettyTable

warnings.filterwarnings(action='ignore')
np.random.seed(20)

X_n ,y_n = make_gaussian_quantiles(mean=(1, 2), cov=3 ,n_samples=2500,
                               n_features=2, n_classes=1)


X_p ,y_p = make_gaussian_quantiles(mean=(-1, -2), cov=3 ,n_samples=100,
                               n_features=2, n_classes=1)
y_p = np.ones(len(y_p))


data_n = np.hstack((X_n, y_n.reshape(-1, 1)))
data_p = np.hstack((X_p, y_p.reshape(-1, 1)))
data_prev = np.vstack((data_n, data_p))

index_ = np.random.permutation(data_prev.shape[0])
data = data_prev[index_, :]
X = data[:, 0: 1]
y = data[:, 2]

lr = LogisticRegression()
lr.fit(X, y)

pred = lr.predict(X)

table = PrettyTable(['score', 'value'])
table.align = 'l'
table.add_row(['precision', precision_score(y, pred)])
table.add_row(['accuracy', accuracy_score(y, pred)])
table.add_row(['recall_score', recall_score(y, pred)])
print(table)

x_range = np.arange(-6, 6, 0.5)
line = [lr.coef_[0] * _ + lr.intercept_[0] for _ in  x_range]

plt.figure(figsize=(8, 6))
plt.plot(x_range, line, color='green', label='pred_line', linewidth=3)
plt.scatter(X_n[:, 0], X_n[:, 1], linewidths=0.00001, label='neg_sam')
plt.scatter(X_p[:, 0], X_p[:, 1], linewidths=0.02, label='pos_sam')
plt.grid(True)
plt.legend()
plt.title('Not weight samples \n function={0}*x {1}'.
          format(round(lr.coef_[0][0], 2), round(lr.intercept_[0], 2)), 
          fontsize=20)
plt.show()

在这里插入图片描述
在这里插入图片描述

3.2.5 设置损失函数权重

  1. 设置损失函数的权重,使得少数类别判断错误的损失大于多数类别数据判断错误的损失。即当我们少数类别数据判断错误的时候,会产生一个较大的损失值,从而导致模型参数往少数类别数据预测准确的方向偏。可以通过scikit-learn中的class_weight参数来设置权重。

在这里插入图片描述

# 只有在分类任务中才有
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC

class_weight = {0: 0.5, 1: 5}
model = LogisticRegression(class_weight=class_weight)
model = KNeighborsClassifier(weights='distance')
model = SVC(class_weight=class_weight)

下采样/欠采样

  1. 下采样/欠采样(under sampling):从多数类别中随机抽取样本从而减少多数类别的样本数据,使得数据达到平衡的方式。
    在这里插入图片描述
  2. 集成下采样: 采用普通的下采样方式会导致信息丢失,因此一般会采用集成下采样和下采样结合的方式来解决这个问题,主要有两种方式:
    1. EasyEnsemble
      1. 采用不放回的数据抽取方式抽取多数类别样本数据,然后将抽取出来的数据和少数类别数据组合训练一个模型;多次进行这样的操作,从而构建多个模型,然后使用多个模型公共决策/预测。
    2. BalaceCascade
      1. 利用Boosting这种增量思想来训练模型;先采用下采样产生训练集,然后使用AdaBoost算法训练一个分类器;然后使用该分类器对所有的多数类别样本进行预测,并将预测正确的样本从多数类别数据中删除;重复迭代上述两个操作,直到大众样本数据量等于小众样本数据量;
# 可以使用pandas
import pandas as pd
import numpy as np

sample_num = 100
rng = np.random.RandomState(20)
data = rng.randn(sample_num, 2)
df = pd.DataFrame(data, columns=['x1', 'x2'])
df.sample(frac=0.5, replace=False, weights=None,)

# 可以使用sklearn
from sklearn.model_selection import train_test_split
tmp = pd.DataFrame([0]*sample_num)
extracted_x, rest_x, _, _ = train_test_split(df, tmp, test_size=0.5)

# 第三种用random.randint来抽样
index_ = rng.randint(0, sample_num-1, int(sample_num*0.5))
sampled = df.iloc[index_, :]
print(sampled.shape)

3.2.6 Edited Nearest Neighbor(ENN)

对于多数类别的样本数据而言,如果这个样本的大部分K近邻样本和自身类别不一样,那我们就将其删除,然后使用删除后的样本训练模型。
在这里插入图片描述由于两类样本边界处的多数类别样本,分别能力并不强,可以将其从多数类别样本中删除。

3.2.7 Repeated Edited Nearest Neighbor(RENN)

  1. 对于多数类别的样本而言,如果这个样本的大部分k近邻样本和自身类别不一样,那我们就将其删除;重复性的进行上述的删除操作,直到数据集无法在被删除后,使用此时的数据集训练模型。
  2. 相比ENN,这个需要进行多次重复删除,直到无法删除为止;
    在这里插入图片描述
# RENN实现方案
import pandas as pd
import numpy as np
#  生成训练数据

pos_num = 10000
neg_num = 100
n_features = 5

rng = np.random.RandomState(10)

pos_x = rng.randint(0, 20, [pos_num, n_features])
pos_y = np.ones((pos_num, 1))
pos_df = np.concatenate((pos_x, pos_y), axis=1)
print('正样本大小:', pos_df.shape)

neg_x = rng.randint(0, 20, [neg_num, n_features])
neg_y = np.zeros((neg_num, 1))
neg_df = np.concatenate((neg_x, neg_y), axis=1)
print('负样本大小:', neg_df.shape)

raw_array = np.concatenate((pos_df, neg_df), axis=0)
cols = ['x{}'.format(_) for _ in range(n_features)] + ['label']
raw_df = pd.DataFrame(raw_array, columns=cols)
print(raw_df.head(2))

3.2.8 Tomek Line Removal

如果两个不同类别的样本,它们的最近邻都是对方,也就是A的最近邻是B,B的最近邻是A,那么A、B就是Tomek Link。将所有Tomek Link中多数类别的样本删除。然后使用删除后的样本训练模型。
在这里插入图片描述

3.2.9 过采样/上采样

  1. 和欠采样采用相同的原理,通过抽样来增加少数样本的数目,从而 达到数据平衡的目的。一种简单的方式就是通过又放回的抽样,不断地从少数类别样本数据中抽取样本,然后使用抽取样本+原始数据组成训练数据集来训练模型;不过该方式容易导致过拟合,一般抽样样本不要超过50%。
    在这里插入图片描述
  2. 因为在上采样过程中,是进行随机又放回的抽样。所以最终模型中,数据其实是存在一定的重复数据,为了防止这个重复数据导致的问题,我们可以加入一定的随机性。也就是说: 再抽取数据后,对数据的各个维度可以进行随机的小范围变动,eg:(1, 2, 3)—>(1.01, 1.99, 3);通过该方式可以相对比较容易的降低上采样导致的过拟合问题。

3.2.10 Smote 采样

  可以采用数据合成的方式来生成更多的样本,该方式在小数据集下有比较成功的案例,常见的算法是SMOTE采样,该算法利用小众样本在空间上的相似性来生成新样本。
在这里插入图片描述

3.2.11异常点监测

  对于正负样本极不平衡的情况下,其实可以换一中思路/角度来解决问题:可以将其看成一分类(One class Learning)或者异常点监测问题(Novelty Detection)问题。在这类算法的应用过程中,主要就是对一个类别进行建模,然后对所以不属于这个类别特征的数据就认为是异常数据,经典算法包括:One-Class-SVM、IsoLationForest等,特别是应用在风控领域。
在这里插入图片描述

3.2.12数据不平衡问题解决

  1. 首先考虑在模型训练的函数中设置class_weight参数;
  2. 看正负样本比例占比
    1. 比例非常悬殊,用异常点监测算法;
    2. 比例为1:10 到1: 100之间, 用上采样,到达到1:2的情况下即可,大多用SMOTE算法。
    3. 比例为1:100到1:1000之间, 采用下采样;
    4. 比例为1:10以内,修改class_weight即可;

3.3 文本特征转换

机器学习的模型算法均要求输入的数据类型必须是数值型,所以对于文本类型的特征属性,需要进行文本数据转换,也就是将需要的文本数据转换成数值型数据。常用的方式如下:
1、词袋法(BOW/TF)
2. TF-IDF(Ierm frequency-inverse document frequency)
3. HashTF
4. Word2Vec

3.4 缺省值填充

缺省值是数据中最常见的一个问题,处理缺省值有很多方式,主要包括以下四个步骤进行缺省值处理:

  1. 确定缺省值范围
  2. 去除不需要的字段【重要性不大】
  3. 填充缺省值内容
  4. 重新获取数据
    注意:最重要的是缺省值内容填充。
3.4.1 缺省值填充
  1. 在进行确定缺省范围的时候,对每一个字段都计算其缺失比例,然后按照缺失比例和字段重要性分别指定不同的策略:
    1. 重要性高,缺失率低:
      1. 通过计算进行填充;
      2. 通过经验或者业务知识估计
    2. 重要性高,缺失率高:
      1. 尝试从其他渠道取数补全
      2. 使用其他字段通过计算获取;
      3. 去除字段,并在结果中标明;
    3. 重要性低,缺失率低:
      1. 不做处理或者简单填充
    4. 重要性低,缺失率高:
      1. 直接去除该字段。
  2. 在进行去除不需要的字段时,需要注意的是:删除操作最好不要直接操作于原始数据上,最好是抽取部分数据进行字段删除后的模型构建,查看模型效果,如果效果不错,那么再到全量数据上进行删除字段操作。总而言之:该过程简单,但是必须慎用,不过一般效果不错,删除一个丢失率高一级重要性低的数据可以降低模型的训练复杂度,同时又不会降低模型的效果。
  3. 填充缺省值是一个比较重要的过程,也是我们常用的一种缺省值解决方案,一般采用下面几种方式进行数据的填充:
    1. 以业务知识或者经验推测填充缺失值;
    2. 以同一字段指标的计算结果(均值、中位数、众数等)填充缺失值;
    3. 以不同字段指标的计算结果来推测性的填充缺省值,比如通过身份证号码计算年龄、通过收货地址来推进测家庭住址、通过访问的IP地址来推测家庭/公司/学校的家庭住址等等。
  4. 如果某些指标非常重要,但是缺失率又比较高,而且通过其他字段没法比较精准的计算出指标的情况下,那么就需要和数据生产方(业务人员、数据收集人员等)沟通协作,是否可以通过其他的渠道来获取相关的数据,也就是重新获取数据的操作。
  5. 对于缺省值的数据,在处理之前一定要进行预处理操作,一般采用中位数、均值或者众数来进行填充,在scikit中主要通过Imputer来来实现对缺省值的填充。
# Inputer API
from sklearn.preprocessing import Imputer
import numpy as np

param_dist = {
    'missing_values': ['NaN', 1],
    'strategy': ['mean', 'median', 'most_frequent'],
    'axis': [0, 1]}

"""
fit()
fit_transform()
get_params()
set_params()
transform()
"""
X = [
    [2, 2, 4, 1],
    [np.nan, 3, 4, 1],
    [1, 1, 1, np.nan],
    [2, 2, np.nan, 3]
]

X2 = [
    [2, 6, np.nan, 1],
    [np.nan, 5, np.nan, 1],
    [4, 1, np.nan, 5],
    [np.nan, np.nan, np.nan, 1]
]

mean = []
for j in range(len(X[0])):
    cnt = 0
    sum_ = 0
    for i in range(len(X)):
        if np.isnan(X[i][j]):
            continue
        else:
            cnt += 1
            sum_ += X[i][j]
    mean.append(sum_/cnt)            

print('填充值前置计算:', mean)

# 按照列进行填充值的计算
imputer1 = Imputer(missing_values='NaN',
            strategy='mean', axis=0)
# 按行进行填充值的计算
imputer2 = Imputer(missing_values='NaN', strategy='mean', axis=1)

imputer1.fit(X)
imputer2.fit(X)

for imp in [imputer1, imputer2]:
    if imp.get_params()['axis'] == 0:
        print('X 的每个特征属性的填充值为:')
        print(imp.statistics_)

X2_ = imputer1.transform(X2)
print(X2_)

在这里插入图片描述

3.5 哑编码

  1. 哑编码(OneHotEncoder):对于定性的数据(也就是分类的数据)可以采用N位的状态寄存器来对N个状态进行编码,每一个状态都有一个独立的寄存器位,并且在任意状态下只有一位有效,是一种常用的数字特征化的方式。比如有一个特征属性:[‘male’, female’],那么male使用向量[1, 0]表示,female使用[0, 1]表示。
  2. 几种方法说明
    1. 如果是sklearn中自带的onehotencoder,那么必须是数值型的;
    2. 对于非数值型,用pd.Categorical(xxx)线转换成数值型
    3. 也可以用pd.dummies()来转换。
import numpy as np
import pandas as pd

from sklearn.preprocessing import OneHotEncoder
from sklearn.feature_extraction import DictVectorizer
from sklearn.feature_extraction import FeatureHasher

# 如果只对其中的某一列做哑编码:categorical_features=[2]
enc = OneHotEncoder()
a = np.array([
    [0, 0, 3],
    [1, 1, 0],
    [0, 2, 1],
    [1, 0, 2],
    [1, 1, 1]
])

enc.fit(a)
print('每一列特征分别有多少个:')
print(enc.n_values_)

"""
1. 对于字符串类型的数值无法转换, 
在转换以前必须用pd.Categorical(xxx)转换成数值型,然后再进行oneHot编码
2. 用pandas中的pd.get_dummies()

"""

3.4.2 二值化

  1. 二值化(Binarizer):对于定量的数据(特征取值连续)根据给定的阈值将其进行转换,如果大于阈值,那么赋值为1,否则赋值为0。
  2. 备注:
    1. 一般情况下,对于每个特征需要不同的阈值进行操作,所以我们会拆分成几个DataFrame进行二值化操作,再将数据进行合并;
    2. 一般情况下,在对数据进行划分的时候,不是进行二值化,而是进行多值化(分区划/分桶化);即,将一个连续的数据,按照不同的取值范围,分为不同的级别;比如,在某一个模型中,存在收入情况的特征,根据业务来判断的话,可能会得到一箱因变量的因素其实是区间后的收入情况,那么这个时候就可以基于业务的特征,将收入划分为收入等级,比如:
收入区间等级
<w0
1w-2w1
2w-3w2
>3w3
# demo
from sklearn.preprocessing import Binarizer
import numpy as np

rng = np.random.RandomState(10)
test  = rng.randn(4, 5)

# 所有的特征属性选择同样的阈值
binarizer = Binarizer(threshold=0.01)
binarizer.fit(test)
trans = binarizer.transform(test)
print('原始数据:', test)
print('转化后的:', trans)

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值