本笔记为阿里云天池龙珠计划数据挖掘训练营的学习内容,链接为:
三、 特征工程目标
Tip:此部分为零基础入门数据挖掘的 Task3 特征工程部分,带你来了解各种特征工程以及分析方法,欢迎大家后续多多交流。
赛题:零基础入门数据挖掘 - 二手车交易价格预测
项目地址:https://github.com/datawhalechina/team-learning-data-mining/tree/master/SecondHandCarPriceForecast
比赛地址:零基础入门数据挖掘 - 二手车交易价格预测_学习赛_天池大赛-阿里云天池的赛制
3.1 特征工程目标
-
对于特征进行进一步分析,并对于数据进行处理
-
完成对于特征工程的分析,并对于数据进行一些图表或者文字总结并打卡。
3.2 内容介绍
常见的特征工程包括:
- 异常处理:
- 通过箱线图(或 3-Sigma)分析删除异常值;
- BOX-COX 转换(处理有偏分布);
- 长尾截断;
- 特征归一化/标准化:
- 标准化(转换为标准正态分布);
- 归一化(转换到 [0,1] 区间);
- 针对幂律分布,可以采用公式:
- 数据分桶:
- 等频分桶;
- 等距分桶;
- Best-KS 分桶(类似利用基尼指数进行二分类);
- 卡方分桶;
- 缺失值处理:
- 不处理(针对类似 XGBoost 等树模型);
- 删除(缺失数据太多);
- 插值补全,包括均值/中位数/众数/建模预测/多重插补/压缩感知补全/矩阵补全等;
- 分箱,缺失值一个箱;
- 特征构造:
- 构造统计量特征,报告计数、求和、比例、标准差等;
- 时间特征,包括相对时间和绝对时间,节假日,双休日等;
- 地理信息,包括分箱,分布编码等方法;
- 非线性变换,包括 log/ 平方/ 根号等;
- 特征组合,特征交叉;
- 仁者见仁,智者见智。
- 特征筛选
- 过滤式(filter):先对数据进行特征选择,然后再训练学习器,常见的方法有 Relief/方差选择法/相关系数法/卡方检验法/互信息法;
- 包裹式(wrapper):直接把最终将要使用的学习器的性能作为特征子集的评价准则,常见方法有 LVM(Las Vegas Wrapper) ;
- 嵌入式(embedding):结合过滤式和包裹式,学习器训练过程中自动进行了特征选择,常见的有 lasso 回归;
- 降维
- PCA/ LDA/ ICA;
- 特征选择也是一种降维。
3.3 代码示例
3.3.0 导入数据
import pandas as pdimport numpy as npimport matplotlibimport matplotlib.pyplot as pltimport seaborn as sns%matplotlib inlinepath = './data/'## 1) 载入训练集和测试集;train = pd.read_csv(path+'train.csv', sep=' ')test = pd.read_csv(path+'testA.csv', sep=' ')print(train.shape)print(test.shape)
(150000, 31) (50000, 30)
train.head()
[3]:
, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
SaleID | name | regDate | model | brand | bodyType | fuelType | gearbox | power | kilometer | ... | v_5 | v_6 | v_7 | v_8 | v_9 | v_10 | v_11 | v_12 | v_13 | v_14 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 736 | 20040402 | 30.0 | 6 | 1.0 | 0.0 | 0.0 | 60 | 12.5 | ... | 0.235676 | 0.101988 | 0.129549 | 0.022816 | 0.097462 | -2.881803 | 2.804097 | -2.420821 | 0.795292 | 0.914762 |
1 | 1 | 2262 | 20030301 | 40.0 | 1 | 2.0 | 0.0 | 0.0 | 0 | 15.0 | ... | 0.264777 | 0.121004 | 0.135731 | 0.026597 | 0.020582 | -4.900482 | 2.096338 | -1.030483 | -1.722674 | 0.245522 |
2 | 2 | 14874 | 20040403 | 115.0 | 15 | 1.0 | 0.0 | 0.0 | 163 | 12.5 | ... | 0.251410 | 0.114912 | 0.165147 | 0.062173 | 0.027075 | -4.846749 | 1.803559 | 1.565330 | -0.832687 | -0.229963 |
3 | 3 | 71865 | 19960908 | 109.0 | 10 | 0.0 | 0.0 | 1.0 | 193 | 15.0 | ... | 0.274293 | 0.110300 | 0.121964 | 0.033395 | 0.000000 | -4.509599 | 1.285940 | -0.501868 | -2.438353 | -0.478699 |
4 | 4 | 111080 | 20120103 | 110.0 | 5 | 1.0 | 0.0 | 0.0 | 68 | 5.0 | ... | 0.228036 | 0.073205 | 0.091880 | 0.078819 | 0.121534 | -1.896240 | 0.910783 | 0.931110 | 2.834518 | 1.923482 |
5 rows × 31 columns
train.columns
[4]:
Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType', , 'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode', , 'seller', 'offerType', 'creatDate', 'price', 'v_0', 'v_1', 'v_2', 'v_3', , 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12', , 'v_13', 'v_14'], , dtype='object')
test.columns
[5]:
Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType', , 'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode', , 'seller', 'offerType', 'creatDate', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', , 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12', 'v_13', , 'v_14'], , dtype='object')
3.3.1 删除异常值
# 这里我包装了一个异常值处理的代码,可以随便调用。对于category类型特征不适用
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)# 当rule[0]和rule[1]中每个位置上有一个值为True时,返回对应index,既当前特征中该位置的值为异常值,[ ]中需为Series类型
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
# 我们可以删掉一些异常数据,以 power 为例。 power特征没有极小异常值
# 这里删不删同学可以自行判断# 但是要注意 test 的数据不能删 = = 不能掩耳盗铃是不是train = outliers_proc(train, 'power', scale=3)
Delete number is: 963 Now column number is: 149037 Description of data less than the lower bound is: count 0.0 mean NaN std NaN min NaN 25% NaN 50% NaN 75% NaN max NaN Name: power, dtype: float64 Description of data larger than the upper bound is: count 963.000000 mean 846.836968 std 1929.418081 min 376.000000 25% 400.000000 50% 436.000000 75% 514.000000 max 19312.000000 Name: power, dtype: float64
3.3.2 特征构造
# 训练集和测试集放在一起,方便构造特征train['train']=1test['train']=0# 将训练集和测试集拼接,并重置索引
data = pd.concat([train, test], ignore_index=True, sort=False)# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比# 不过要注意,数据里有时间出错的格式,所以我们需要 errors='coerce',遇到不满足格式的日期时置空(NaT),当creatDate和regDate中有一个为NaT时,两个特征的差同样为NaT
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')).dt.days# 看一下空数据,有 15k 个样本的时间是有问题的,我们可以选择删除,也可以选择放着。# 但是这里不建议删除,因为删除缺失数据占总样本量过大,7.5%# 我们可以先放着,因为如果我们 XGBoost 之类的决策树,其本身就能处理缺失值,所以可以不用管;data['used_time'].isnull().sum()
[10]:
15072
# 从邮编中提取城市信息,因为是德国的数据,所以参考德国的邮编,相当于加入了先验知识data['city'] = data['regionCode'].apply(lambda x : str(x)[:-3])# 计算某品牌的销售统计量,同学们还可以计算其他特征的统计量# 这里要以 train 的数据计算统计量,没有使用拼接后的数据计算统计量,屏蔽测试集信息
train_gb = train.groupby("brand")all_info = {}# 按brand分组后,逐个取每个分组
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 # 将某品牌的统计量加入字典all_info
# 将品牌统计量转化为数据帧,并进行转置、重置索引、修改列名操作
brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "brand"})# 将品牌统计量数据帧拼接到data上,以data的brand为主键
data = data.merge(brand_fe, how='left', on='brand')# 数据分桶以 power 为例# 这时候我们的缺失值也进桶了,# 为什么要做数据分桶呢,原因有很多,= =# 1. 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;# 2. 离散后的特征对异常值更具鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;# 3. LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;# 4. 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量变成 M*N 个变量,进一步引入非线形,提升了表达能力;# 5. 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化# 当然还有很多原因,LightGBM 在改进 XGBoost 时就增加了数据分桶,增强了模型的泛化性bin = [i*10 for i in range(31)]# 将power特征值分入31个桶之内,使连续型特征值离散化
data['power_bin'] = pd.cut(data['power'], bin, labels=False)data[['power_bin', 'power']].head()
[13]:
power_bin | power | |
---|---|---|
0 | 5.0 | 60 |
1 | NaN | 0 |
2 | 16.0 | 163 |
3 | 19.0 | 193 |
4 | 6.0 | 68 |
# 利用好了,就可以删掉原始数据了data = data.drop(['creatDate', 'regDate', 'regionCode'], axis=1)print(data.shape)data.columns
(199037, 39)
[15]:
Index(['SaleID', 'name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', , 'power', 'kilometer', 'notRepairedDamage', 'seller', 'offerType', , 'price', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', , 'v_9', 'v_10', 'v_11', 'v_12', 'v_13', 'v_14', 'train', 'used_time', , 'city', 'brand_amount', 'brand_price_average', 'brand_price_max', , 'brand_price_median', 'brand_price_min', 'brand_price_std', , 'brand_price_sum', 'power_bin'], , dtype='object')
# 目前的数据其实已经可以给树模型使用了,所以我们导出一下,不保留行索引
data.to_csv('data_for_tree.csv', index=0)# 我们可以再构造一份特征给 LR NN 之类的模型用# 之所以分开构造是因为,不同模型对数据集的要求不同# 我们看下数据分布:data['power'].plot.hist()
[17]:
<AxesSubplot:ylabel='Frequency'>
# 我们刚刚已经对 train 进行异常值处理了,但是现在还有这么奇怪的分布是因为 test 中的 power 异常值,
# 所以我们其实刚刚 train 中的 power 异常值不删为好,可以用长尾分布截断来代替
train['power'].plot.hist() # 删除power异常值之后的训练集分布,接近正态分布
[18]:
<AxesSubplot:ylabel='Frequency'>
# 我们对其取log,再做归一化data['power'] = np.log(data['power'] + 1) # +1防止power的值为0
# np.min(data['power'])可以用data['power'].min()
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))data['power'].plot.hist()
[19]:
<AxesSubplot:ylabel='Frequency'>
# km的比较正常,应该是已经做过分桶了data['kilometer'].plot.hist()
[20]:
<AxesSubplot:ylabel='Frequency'>
# 所以我们可以直接做归一化
data['kilometer'] = ((data['kilometer'] - np.min(data['kilometer'])) /
(np.max(data['kilometer']) - np.min(data['kilometer'])))
data['kilometer'].plot.hist()
[21]:
<AxesSubplot:ylabel='Frequency'>
# 除此之外 还有我们刚刚构造的统计量特征:# 'brand_amount', 'brand_price_average', 'brand_price_max',# 'brand_price_median', 'brand_price_min', 'brand_price_std',# 'brand_price_sum'# 这里不再一一举例分析了,直接做变换,def max_min(x):return (x - np.min(x)) / (np.max(x) - np.min(x))data['brand_amount'] = max_min(data['brand_amount'])
data['brand_price_average'] = max_min(data['brand_price_average'])
data['brand_price_max'] = max_min(data['brand_price_max'])
data['brand_price_median'] = max_min(data['brand_price_median'])
data['brand_price_min'] = max_min(data['brand_price_min'])
data['brand_price_std'] = max_min(data['brand_price_std'])
data['brand_price_sum'] = max_min(data['brand_price_sum'])
# 对类别特征进行 OneEncoder,独热编码
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType','gearbox','notRepairedDamage','power_bin'])print(data.shape)data.columns
(199037, 370)
[24]:
Index(['SaleID', 'name', 'power', 'kilometer', 'seller', 'offerType', 'price', , 'v_0', 'v_1', 'v_2', , ... , 'power_bin_20.0', 'power_bin_21.0', 'power_bin_22.0', 'power_bin_23.0', , 'power_bin_24.0', 'power_bin_25.0', 'power_bin_26.0', 'power_bin_27.0', , 'power_bin_28.0', 'power_bin_29.0'], , dtype='object', length=370)
# 这份数据可以给 LR 用data.to_csv('data_for_lr.csv', index=0)
3.3.3 特征筛选
1) 过滤式
# 相关性分析,斯皮尔曼等级相关系数没有皮尔曼相关系数严格,无需两个观测值是正态分布,或者接近正态分布
print(data['power'].corr(data['price'], method='spearman'))print(data['kilometer'].corr(data['price'], method='spearman'))print(data['brand_amount'].corr(data['price'], method='spearman'))print(data['brand_price_average'].corr(data['price'], method='spearman'))print(data['brand_price_max'].corr(data['price'], method='spearman'))print(data['brand_price_median'].corr(data['price'], method='spearman'))
0.5728285196051496 -0.4082569701616764 0.058156610025581514 0.3834909576057687 0.259066833880992 0.38691042393409447
# 当然也可以直接看图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)
[27]:
<AxesSubplot:title={'center':'Correlation of Numeric Features with Price'}>
2) 包裹式
!pip install mlxtend
Defaulting to user installation because normal site-packages is not writeable Looking in indexes: https://mirrors.aliyun.com/pypi/simple Collecting mlxtend Downloading https://mirrors.aliyun.com/pypi/packages/4c/0d/4a73b8bc49e2cfee178fe50dd8e84d5ba817d0b2454b09308397416e0e48/mlxtend-0.17.3-py2.py3-none-any.whl (1.3 MB) |████████████████████████████████| 1.3 MB 1.4 MB/s eta 0:00:011.4 MB/s eta 0:00:01 Requirement already satisfied: scipy>=1.2.1 in /opt/conda/lib/python3.6/site-packages (from mlxtend) (1.5.4) Requirement already satisfied: numpy>=1.16.2 in /opt/conda/lib/python3.6/site-packages (from mlxtend) (1.19.1) Requirement already satisfied: scikit-learn>=0.20.3 in /opt/conda/lib/python3.6/site-packages (from mlxtend) (0.23.2) Collecting pandas>=0.24.2 Downloading https://mirrors.aliyun.com/pypi/packages/4d/51/bafcff417cd857bc6684336320863b5e5af280530213ef8f534b6042cfe6/pandas-1.1.4-cp36-cp36m-manylinux1_x86_64.whl (9.5 MB) |████████████████████████████████| 9.5 MB 108.1 MB/s eta 0:00:01 Requirement already satisfied: joblib>=0.13.2 in /opt/conda/lib/python3.6/site-packages (from mlxtend) (0.17.0) Requirement already satisfied: setuptools in /opt/conda/lib/python3.6/site-packages (from mlxtend) (49.6.0) Requirement already satisfied: matplotlib>=3.0.0 in /opt/conda/lib/python3.6/site-packages (from mlxtend) (3.3.3) Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.6/site-packages (from scikit-learn>=0.20.3->mlxtend) (2.1.0) Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.6/site-packages (from pandas>=0.24.2->mlxtend) (2020.4) Requirement already satisfied: python-dateutil>=2.7.3 in /opt/conda/lib/python3.6/site-packages (from pandas>=0.24.2->mlxtend) (2.8.1) Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=3.0.0->mlxtend) (2.4.7) Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=3.0.0->mlxtend) (8.0.1) Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=3.0.0->mlxtend) (0.10.0) Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=3.0.0->mlxtend) (1.2.0) Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.6/site-packages (from python-dateutil>=2.7.3->pandas>=0.24.2->mlxtend) (1.15.0) Installing collected packages: pandas, mlxtend Successfully installed mlxtend-0.17.3 pandas-1.1.4
# k_feature 太大会很难跑,没服务器,所以提前 interrupt 了from mlxtend.feature_selection import SequentialFeatureSelector as SFSfrom sklearn.linear_model import LinearRegressionsfs = SFS(LinearRegression(), # 线性回归算法
k_features=10, # 选择10个特征
forward=True, # 前向选择
floating=False, # 不使用悬浮搜索算法,一般和前向选择配套使用
scoring = 'r2', # 评估指标,用于回归模型
cv = 0) # 不使用交叉验证
x = data.drop(['price'], axis=1) # 将price列剔除后赋值给x
numerical_cols = x.select_dtypes(exclude = 'object').columns # 如果包含object类型特征,sfs会报错
x = x[numerical_cols]x = x.fillna(0) # 用0填充缺失值
y = data['price'].fillna(0)sfs.fit(x, y)sfs.k_feature_names_
[31]:
('kilometer', , 'v_0', , 'v_3', , 'v_7', , 'train', , 'used_time', , 'brand_price_average', , 'brand_price_std', , 'model_167.0', , 'gearbox_1.0')
# 画出来,可以看到边际效益from mlxtend.plotting import plot_sequential_feature_selection as plot_sfsimport matplotlib.pyplot as plt# 以字典的方式返回LR模型对特征子集各个特征组合进行评估的结果,评估方法:标准偏差
fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_dev')plt.grid()plt.show()
/opt/conda/lib/python3.6/site-packages/numpy/core/_methods.py:234: RuntimeWarning: Degrees of freedom <= 0 for slice keepdims=keepdims) /opt/conda/lib/python3.6/site-packages/numpy/core/_methods.py:226: RuntimeWarning: invalid value encountered in double_scalars ret = ret.dtype.type(ret / rcount)
3) 嵌入式
# 下一章介绍,Lasso 回归和决策树可以完成嵌入式特征选择
# 大部分情况下都是用嵌入式做特征筛选
3.4 经验总结
特征工程是比赛中最至关重要的的一块,特别的传统的比赛,大家的模型可能都差不多,调参带来的效果增幅是非常有限的,但特征工程的好坏往往会决定了最终的排名和成绩。
特征工程的主要目的还是在于将数据转换为能更好地表示潜在问题的特征,从而提高机器学习的性能。比如,异常值处理是为了去除噪声,填补缺失值可以加入先验知识等。
特征构造也属于特征工程的一部分,其目的是为了增强数据的表达。
有些比赛的特征是匿名特征,这导致我们并不清楚特征相互之间的关联性,这时我们就只有单纯基于特征进行处理,比如装箱,groupby,agg 等这样一些操作进行一些特征统计,此外还可以对特征进行进一步的 log,exp 等变换,或者对多个特征进行四则运算(如上面我们算出的使用时长),多项式组合等然后进行筛选。由于特性的匿名性其实限制了很多对于特征的处理,当然有些时候用 NN 去提取一些特征也会达到意想不到的良好效果。
对于知道特征含义(非匿名)的特征工程,特别是在工业类型比赛中,会基于信号处理,频域提取,丰度,偏度等构建更为有实际意义的特征,这就是结合背景的特征构建,在推荐系统中也是这样的,各种类型点击率统计,各时段统计,加用户属性的统计等等,这样一种特征构建往往要深入分析背后的业务逻辑或者说物理原理,从而才能更好的找到 magic。
当然特征工程其实是和模型结合在一起的,这就是为什么要为 LR NN 做分桶和特征归一化的原因,而对于特征的处理效果和特征重要性等往往要通过模型来验证。
总的来说,特征工程是一个入门简单,但想精通非常难的一件事。
Task 3-特征工程 END.
--- By: 阿泽
PS:复旦大学计算机研究生
知乎:阿泽 https://www.zhihu.com/people/is-aze(主要面向初学者的知识整理)