12、特征工程

特征工程

特征工程在机器学习中占据核心地位,它涉及从原始数据中提取有价值的信息,以优化和提高模型的性能。本节旨在深入探讨特征工程的基础理论和实践应用。

特征工程的重要性

特征工程是构建机器学习模型过程中的关键步骤。优质的特征不仅可以显著提高模型的性能,还能减少所需的数据量,从而在处理复杂数据时提高效率和准确性。

特征工程的一般做法

在进行特征工程时,首先需要识别并分类数据特征的类型。主要类型包括连续特征、离散特征、序列特征和多模态特征。本节将主要聚焦于连续特征和离散特征的处理方法。

连续特征的特征工程

连续特征通常是数值型的,可以再任意两个值之间取无数个数值。常见的连续特征处理方法包括:

  • 特征间的二元数学运算:例如,通过特征的加、减、乘、除来创建新的特征。例如,若有特征表示房屋的宽度和长度,可以通过它们的乘积来创建一个表示面积的新特征。
  • 单个特征的处理
    • 标准化:调整特征数据,使其均值为 0,标准差为 1,有助于加速学习算法的收敛,公式:\frac{X - \mu }{\sigma }
    • 归一化:将特征缩放到给定的最小值和最大值之间,常用于将特征值限制在 0 和 1 之间,公式:\frac{X - X_{min}}{X_{max} - X_{min}}
    • 其他数学变换:如对数变换,有助于处理偏态分布的数据。
    • 离散化:将连续特征分割成若干区间,转换为离散特征。常见方法包括等频(每个区间含有相同数量的点)和等距(每个区间长度相同)。
离散特征的特征工程

离散特征通常是分类,表示为一组有限的类别。处理离散特征的重用方法包括:

  • 特征对的处理
    • 特征交叉:结合两个或多个特征创建新特征,例如,结合“城市”和“职业”特征来预测收入。
    • Group特征:根据一个或多个特征进行分组,然后对每组进行汇总统计,如计算平均值、最大值或计数。
  • 单个特征的处理
    • 标签编码(Label Encoding):将类别标签转换为序列数字。
    • 独热编码(One-Hot Encoding):为每个类别创建一个新的二进制特征。
    • 目标编码(Target Encoding):基于目标变量的平均值对类别进行编码。
    • 计数编码(Count Encoding):基于每个类别出现的频率进行编码。

特征工程实践

import pandas as pd
import numpy as np
from faker import Faker
from datetime import datetime, timedelta
from tqdm import tqdm
from sklearn.preprocessing import OneHotEncoder
from category_encoders.hashing import HashingEncoder
 
# 设置要显示的行数和列数
pd.set_option('display.max_rows', 100)
pd.set_option('display.max_columns', 50)
# 初始化工具
fake = Faker()
np.random.seed(42)

# 生成100行数据
data = {
    # 用户信息
    "user_id": [fake.uuid4()[:8] for _ in range(100)],  # 8 位用户标识
    "age": np.random.randint(18, 70, 100),  # 用户年龄
    "gender": np.random.choice(["M", "F", "Other"], 100, p=[0.45, 0.5, 0.05]),  # 性别分类
    "membership_status": np.random.choice(["Active", "Inactive"], 100, p=[0.7, 0.3]),  # 会员状态
    
    # 产品信息
    "product_id": [f"P{str(i).zfill(5)}" for i in range(1000, 1100)],  # 产品唯一 ID
    "category": np.random.choice(["Tops", "Bottoms", "Dresses", "Outerwear", "Accessories"], 100),  # 商品分类
    "color": np.random.choice(["Red", "Black", "White", "Blue", "Beige"], 100),  # 商品颜色
    "size": np.random.choice(["XS", "S", "M", "L", "XL"], 100),  # 商品尺码
    "price": np.round(np.random.uniform(19.9, 199.9, 100), 2),  # 商品价格
    
    # 交易行为
    "purchase_date": [fake.date_between(start_date='-1y', end_date='today') for _ in range(100)],  # 购买日期
    "quantity": np.random.randint(1, 4, 100),  # 购买数量
    "sales_channel": np.random.choice(["Online", "In-store"], 100, p=[0.6, 0.4]),  # 销售渠道
    "promo_code": np.random.choice(["SPRING20", "SUMMER15", "None"], 100, p=[0.3, 0.2, 0.5]),  # 销售代码
     
    # 交互特征
    "rating": np.random.randint(1, 6, 100),  # 用户评分(1-5)
    "return_flag": np.random.choice([0, 1], 100, p=[0.85, 0.15]),  # 退货标志
    "region": np.random.choice(["NA", "EU", "APAC"], 100, p=[0.4, 0.4, 0.2])  # 销售地区
}

# 创建DataFrame
df = pd.DataFrame(data)

# 添加价格相关性逻辑
df.loc[df['category'] == "Accessories", "price"] = np.round(df["price"] * 0.6, 2)
df.loc[df['category'] == "Outerwear", "price"] = np.round(df["price"] * 1.3, 2)

# 保存数据
df.to_csv("hm_style_data.csv", index=False)
print("生成数据示例:")
df.head()

连续特征的特征工程实践
# 连续特征
def num_add(df, f1, f2):
    # add
    df[f'{f1}_add_{f2}'] = df[f1] + df[f2]
    return df

def num_mul(df, f1, f2):
    # mul
    df[f'{f1}_mul_{f2}'] = df[f1] * df[f2]
    return df

def num_div(df, f1, f2):
    # div
    df[f'{f1}_div_{f2}'] = df[f1] / (df[f2] + df[f2].mean())  # 加一个平滑,防止 f2 取值为 0
    return df

def num_log(df, f):
    # log
    df[f'log_{f}'] = np.log(1 + df[f])
    return df

def num_bin(df, f, num_bins=10, bin_type='cut'):
    # bin
    if bin_type == 'cut':
        df[f'{f}_bin_{num_bins}'] = pd.cut(df[f], num_bins, labels=False)  # 按数值范围均匀分箱,等距
    else:
        df[f'{f}_bin_{num_bins}'] = pd.qcut(df[f], num_bins, labels=False)  # 按样本分位数分箱,等频
    return df
df = num_bin(df, 'age', 10, bin_type='qcut')
df.head()

离散特征的特征工程实践
# 离散变量特征工程
def one_hot_enc(df, f):
    # one-hot
    enc = OneHotEncoder()
    enc.fit(df[f].values.reshape(-1, 1))
    one_hot_array = enc.transform(df[f].values.reshape(-1, 1)).toarray()
    df[[f'{f}_one_hot_{i}' for i in range(one_hot_array.shape[-1])]]= one_hot_array
    return df

def hash_enc(df, f_list, n_components=8):
    # hash enc
    ce_encoder = HashingEncoder(cols=f_list, n_components=n_components).fit(df)  # hash_method='md5'  # 哈希算法(可选:'md5', 'sha1', 'sha256')
    df = ce_encoder.transform(df)
    df.rename(columns=dict(zip([f'col_{i}' for i in range(n_components)], [f'hash_enc_{i}' for i in range(n_components)])), inplace=True)
    return df

def count_enc(df, f):
    # count enc
    map_dict = dict(zip(df[f].unique(), range(df[f].nunique())))
    df[f'label_enc_{f}'] = df[f].map(map_dict).fillna(-1).astype('int32')
    df[f'{f}_count'] = df[f].map(df[f].value_counts())
    return df

def cross_enc(df, f1, f2):
    # cross enc
    print('====================================== {} {} ======================================'.format(f1, f2))
    if f'{f1}_count' not in df.columns:
        df = count_enc(df, f1)
    if f'{f2}_count' not in df.columns:
        df = count_enc(df, f2)
    df[f'{f1}_{f2}'] = df[f1].astype('str') + '_' + df[f2].astype('str')
    df = count_enc(df, f'{f1}_{f2}')
    df[f'{f1}_{f2}_count_div_{f1}_count'] = df[f'{f1}_{f2}_count'] / (df[f'{f1}_count'] + df[f'{f1}_count'].mean())
    df[f'{f1}_{f2}_count_div_{f2}_count'] = df[f'{f1}_{f2}_count'] / (df[f'{f2}_count'] + df[f'{f2}_count'].mean())
    del df[f'{f1}_{f2}']
    return df

def group_stat(df, cat_fea, num_fea):
    for stat in tqdm(['min', 'max', 'mean', 'median', 'std', 'skew']):
        df[f'{cat_fea}_{num_fea}_groupby_{stat}'] = df.groupby(cat_fea)[num_fea].transform(stat)
    return df
df = one_hot_enc(df, 'region')
df[[f'region_one_hot_{i}' for i in range(df['region'].nunique())]].head()

df = hash_enc(df, ['category', 'color', 'size'])
df[[f'hash_enc_{i}' for i in range(8)]].head()

# 离散特征编码
cate_fea_list = ['user_id','product_id','sales_channel', 'promo_code']

# label enc & count enc
for col in tqdm(cate_fea_list):
    df = count_enc(df, col)

# 交叉特征
df = cross_enc(df, 'user_id', 'product_id')
df = cross_enc(df, 'user_id', 'promo_code')
df = cross_enc(df, 'age', 'product_id')

# group 特征
df = group_stat(df, 'user_id', 'price')
df = group_stat(df, 'age', 'price')

df.info()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值