【Kaggle学习笔记】 | Feature Engineering

有这么一句话在业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。

特征工程其本质是一项工程活动,目的是最大限度地从原始数据中提取特征以供算法和模型使用。本期 Kaggle 课程将以 Kickstarter 项目为例进行特征工程入门知识的讲解

数据观察

# 加载并查看数据
import pandas as pd
ks = pd.read_csv('../input/kickstarter-projects/ks-projects-201801.csv',
                 parse_dates=['deadline', 'launched'])
ks.head(10)
# 分析数据
pd.unique(ks.state) # 以数据集里的state列作为输出列,观察state取值有几类取值
ks.groupby('state')['ID'].count() # 返回每类取值的个数,清楚地了解成功记录的比例

数据处理

  1. 简单的数据处理
# 删掉state等于live的记录
ks = ks.query('state != "live"')
# 新增一条输出列,successful记为1,其余记为0
ks = ks.assign(outcome=(ks['state'] == 'successful').astype(int))
# 将时间戳数据格式的launched列里的年、月、日、小时提取出来并以新增列表示
ks = ks.assign(hour=ks.launched.dt.hour,
               day=ks.launched.dt.day,
               month=ks.launched.dt.month,
               year=ks.launched.dt.year)
# 通过LabelEncoder对类别、货币、国家三列数据编码为整数,供模型使用
from sklearn.preprocessing import Lab0elEncoder
cat_features = ['category', 'currency', 'country']
encoder = LabelEncoder()
encoded = ks[cat_features].apply(encoder.fit_transform)
# 合并已处理的整数型数据
data = ks[['goal', 'hour', 'day', 'month', 'year', 'outcome'].join(encoded)
  1. 数据编码:Count Encoding、Target Encoding、CatBoost Encoding

(1)Count Encoding:根据类别在训练集中的频次,转换为对应的数值,例如在 country 列里 GB 字段出现了10次,则 GB 这个字段将会用10来代替,采用先 fit 后 teansform 方法

(2)Target Encoding:用均值来代替类别数据,例如特征值 “CA”,你要计算所有 “CA” 行的 label(即outcome列)的均值,用该均值来替换 “CA”。这种编码方法会产生新的特征,不能把验证集和测试集拿进来 fit,会产生数据泄露,所以现在训练集上先 fit 再应用到其他数据集

(3)CatBoost Encoding:类似于目标编码,因为它基于给定值的目标概率,但对于CatBoost,对于每一行,目标概率仅从其前面的行计算。

# (1) 计数编码
import category_encoders as ce
cat_features = ['category', 'currency', 'country'] 
# 建立编码器
count_enc = ce.CountEncoder()
# 拟合并转换数据
count_encoded = count_enc.fit_transform(ks[cat_features])
# 合并到数据集
data = baseline_data.join(count_encoded.add_suffix("_count"))
# (2) 目标编码
import category_encoders as ce
cat_features = ['category', 'currency', 'country']
# 建立编码器
target_enc = ce.TargetEncoder(cols=cat_features)
train, valid, _ = get_data_splits(data) # 划分训练、验证、测试集,已定义一个函数来实现
# 在训练集上拟合数据
target_enc.fit(train[cat_features], train['outcome'])
# 特征转换为具体数值,并再加上后缀名后与原数据合并
train = train.join(target_enc.transform(train[cat_features]).add_suffix('_target'))
valid = valid.join(target_enc.transform(valid[cat_features]).add_suffix('_target'))
# (3) CatBoost编码
cat_features = ['category', 'currency', 'country']
cb_enc = ce.CatBoostEncoder(cols=cat_features)
train, valid, _ = get_data_splits(data)
# 在训练集上拟合数据
cb_enc.fit(train[cat_features], train['outcome'])
# 特征转换为具体数值,并再加上后缀名后与原数据合并
train = train.join(cb_enc.transform(train[cat_features]).add_suffix('_cb'))
valid = valid.join(cb_enc.transform(valid[cat_features]).add_suffix('_cb'))
  1. 特征建立

    有些情况由于特征数量过少,导致模型性能很差,所以这时候就要建立新的特征,达到数据增强的效果

    (1)组合分类型数据:如一条记录里的 country 值为 CA,category 值为 Music,就可以新建一项特征,值为 CA_Music,这种交互特征可以提供有关分类变量之间的相关性信息,但会得到递减的回报

    (2)自己新增特征,如例子中每个项目前7天启动的项目数等

    (3)改变数值分布特征:当特征是正态分布时,一些模型能工作得更好

# (1) 组合分类数据
# 第一种方法:简单两列相加
interactions = ks['category'] + "_" + ks['country']
# 数据编码
label_enc = LabelEncoder()
data_interaction = baseline_data.assign(category_country=label_enc.fit_transform(interactions))

# 第二种方法:设置多列,每两列相加
import itertools
cat_features = ['ip', 'app', 'device', 'os', 'channel']
interactions = pd.DataFrame(index=clicks.index)
for col1, col2 in itertools.combinations(cat_features, 2):
        new_col_name = '_'.join([col1, col2])
        # 将值变成字符串以相加
        new_values = clicks[col1].map(str) + "_" + clicks[col2].map(str)
	    # 编码
        encoder = preprocessing.LabelEncoder()
        interactions[new_col_name] = encoder.fit_transform(new_values)
# (2) 自己新建特征
# 特征1:每项项目启动时前7天启动的项目数
# 建立以启动时间为索引,编号为值的Series
launched = pd.Series(ks.index, index=ks.launched, name="count_7_days").sort_index()
count_7_days = launched.rolling('7d').count() - 1 # 以目标记录为基点,记录前7天的启动项目数
count_7_days.index = launched.values # 变为值为7天启动项目数,索引为编号
count_7_days = count_7_days.reindex(ks.index) # 重新排序索引,使其能和数据集合并
# 特征2:每个项目从启动时,与上一个同类别项目启动的时间间隔
def time_since_last_project(series):
    # 返回相隔的小时时间
    return series.diff().dt.total_seconds() / 3600. # diff为差分
df = ks[['category', 'launched']].sort_values('launched') 
timedeltas = df.groupby('category').transform(time_since_last_project)
timedeltas = timedeltas.fillna(timedeltas.median()).reindex(baseline_data.index) # 以均值代替NaN并重新排序索引
# (3) 改变数值分布特征:可通过画图来判断怎样改变能使数据近似呈正态分布
plt.hist(ks.goal, range=(0, 100000), bins=50); # 原值
plt.hist(np.sqrt(ks.goal), range=(0, 400), bins=50); # 平方根
plt.hist(np.log(ks.goal), range=(0, 25), bins=50); # 对数
  1. 特征选择

    由于特征过多会导致过拟合和训练和优化速度变久的结果,所以需要一定程度上的特征选择来保留最重要的特征,加强模型性能

    (1)基于单变量统计测试:对每个特征,通过统计参数(误差、方差等)来衡量目标对特征的相关程度,从而以此为依据选择特征,为防止数据泄露,只在训练集上进行选择

    (2)L1 正则化:通过一个模型对所有特征进行选择,而不是只明确地保留几组特征,而是保留所有特征,但不重要的特征会近似于 0。

# (1) 用 scikit-learn feature selection 模块实现,有 [Math Processing Error](误差)、ANOVA(反差)、F-value(线性相关性) 三种选择依据
from sklearn.feature_selection import SelectKBest, f_classif
train, valid, _ = get_data_splits(baseline_data)
# 得到除输出结果外的特征数据集
feature_cols = baseline_data.columns.drop('outcome')
# 建立以 f_classif 为选择依据保留 5 个特征的选择器
selector = SelectKBest(f_classif, k=5)
# 拟合并转换数据
X_new = selector.fit_transform(train[feature_cols],baseline_data['outcome'])
# 返回一个与原来特征数相等但只有选择的特征值不为0,其余值为0的数据集,查看选择的特征是哪些
selected_features = pd.DataFrame(selector.inverse_transform(X_new), 
                                 index=train.index, 
                                 columns=feature_cols)
# 只保留选择的特征列
selected_columns = selected_features.columns[selected_features.var() != 0]
# (2) L1 正则化惩罚项为特征绝对值的和,用于数据选择,L2 正则化惩罚项为特征值的平方和,用于防止过拟合
from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import SelectFromModel
train, valid, _ = get_data_splits(baseline_data)
# 设定特征 X 和输出 y
X, y = train[train.columns.drop("outcome")], train['outcome']
# 设置正则化参数
logistic = LogisticRegression(C=1, penalty="l1", random_state=7).fit(X, y)
model = SelectFromModel(logistic, prefit=True)
X_new = model.transform(X)
# 返回一个与原来特征数相等但只有选择的特征值不为0,其余值为0的数据集,查看选择的特征是哪些
selected_features = pd.DataFrame(selector.inverse_transform(X_new), 
                                 index=train.index, 
                                 columns=feature_cols)
# 只保留选择的特征列
selected_columns = selected_features.columns[selected_features.var() != 0]

数据划分

# 简单的训练集、验证集、测试集划分
valid_fraction = 0.1 # 验证集数据占比10%
valid_size = int(len(data) * valid_fraction) # 验证集具体的大小即记录总数
train = data[:-2 * valid_size]
valid = data[-2 * valid_size:-valid_size]
test = data[-valid_size:]
# 查看每个集输出的平均值,最好结果是3个值接近,说明每个集里数据的种类占比接近
for each in [train, valid, test]:
    print(f"Outcome fraction = {each.outcome.mean():.4f}")

模型构造

# 构造 LightGBM 模型
import lightgbm as lgb
feature_cols = train.columns.drop('outcome') # 自变量的列名
dtrain = lgb.Dataset(train[feature_cols], label=train['outcome']) # 训练集
dvalid = lgb.Dataset(valid[feature_cols], label=valid['outcome']) # 验证集
param = {'num_leaves': 64, 'objective': 'binary'} # 叶数和目标函数
param['metric'] = 'auc' # 
num_round = 1000 # 迭代次数
bst = lgb.train(param,  # 
                dtrain, 
                num_round, 
                valid_sets=[dvalid], 
                early_stopping_rounds=10, # 早停
                verbose_eval=False)  # 迭代多少次打印

输出结果

from sklearn import metrics
ypred = bst.predict(test[feature_cols])
score = metrics.roc_auc_score(test['outcome'], ypred)
print(f"Test AUC score: {score}")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值