Target Mean Encoding

109 阅读4分钟

Target Mean Encoding(目标均值编码)是一种用于处理分类变量的编码技术,它利用目标变量的信息为每个类别创建数值表示。与One-Hot Encoding不同,Target Mean Encoding将每个类别替换为该类别对应的目标变量的平均值。

核心思想

对于每个类别:

类别编码值 = 该类别中目标变量的平均值

例如,在二分类问题中:

类别编码值 = 该类别中正例的比例

为什么使用Target Mean Encoding?

  1. 高效处理高基数特征:对于具有大量类别的特征(如城市、用户ID),One-Hot Encoding会导致维度灾难
  2. 保留目标信息:编码值直接反映类别与目标变量的关系
  3. 改善模型性能:对于基于树的模型(如梯度提升树),通常能带来更好的性能
  4. 减少维度:生成单个数值特征而非多个虚拟变量

基本实现(Python示例)

import pandas as pd

# 示例数据
data = pd.DataFrame({
    'City': ['NY', 'NY', 'SF', 'SF', 'SF', 'LA', 'LA'],
    'Target': [1, 0, 1, 1, 0, 0, 1]
})

# 计算目标均值编码
encoding_map = data.groupby('City')['Target'].mean().to_dict()

# 应用编码
data['City_Encoded'] = data['City'].map(encoding_map)

print(data)

输出:

  City  Target  City_Encoded
0   NY       1         0.5000
1   NY       0         0.5000
2   SF       1         0.6667
3   SF       1         0.6667
4   SF       0         0.3333
5   LA       0         0.5000
6   LA       1         0.5000

过拟合问题与解决方案

直接使用目标均值会导致严重的过拟合问题,特别是当:

  • 类别样本量小时
  • 测试集中出现新类别时

解决方案1:平滑(Smoothing)

引入全局均值作为先验,平衡小样本类别:

编码值 = (n * 类别均值 + α * 全局均值) / (n + α)

其中:

  • n = 类别中的样本数
  • α = 平滑因子(越大,全局均值权重越高)
def smooth_target_encoding(train, column, target, alpha=5):
    # 全局均值
    global_mean = train[target].mean()
    
    # 计算类别统计量
    stats = train.groupby(column)[target].agg(['sum', 'count'])
    
    # 计算平滑后的编码值
    stats['encoded'] = (stats['sum'] + alpha * global_mean) / (stats['count'] + alpha)
    
    return stats['encoded'].to_dict()

# 使用平滑
alpha = 2
encoding_map_smooth = smooth_target_encoding(data, 'City', 'Target', alpha)
data['City_Smooth'] = data['City'].map(encoding_map_smooth)

解决方案2:K-Fold交叉编码

使用交叉验证避免数据泄露:

from sklearn.model_selection import KFold

def kfold_target_encoding(train, column, target, n_splits=5):
    train[column+'_Encoded'] = 0
    kf = KFold(n_splits=n_splits, shuffle=True)
    
    for fold, (train_idx, val_idx) in enumerate(kf.split(train)):
        X_train, X_val = train.iloc[train_idx], train.iloc[val_idx]
        
        # 计算编码映射
        enc_map = X_train.groupby(column)[target].mean()
        
        # 应用编码到验证集
        train.loc[val_idx, column+'_Encoded'] = X_val[column].map(enc_map)
    
    # 处理缺失值(新类别)
    global_mean = train[target].mean()
    train[column+'_Encoded'] = train[column+'_Encoded'].fillna(global_mean)
    
    return train

最佳实践

  1. 始终使用交叉验证方案:防止目标泄露
  2. 平滑处理:特别是对于小样本类别
  3. 处理未知类别:为测试集中的新类别分配全局平均值
  4. 结合其他编码技术:对于线性模型,可结合使用One-Hot Encoding
  5. 正则化:在树模型中使用正则化防止过拟合

完整实现示例

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder

class TargetMeanEncoder:
    def __init__(self, columns, target, alpha=5, kfold=5):
        self.columns = columns
        self.target = target
        self.alpha = alpha
        self.kfold = kfold
        self.encoders = {}
        self.global_mean = None
        
    def fit(self, X, y):
        data = X.copy()
        data[self.target] = y
        self.global_mean = y.mean()
        
        for col in self.columns:
            self.encoders[col] = {}
            
            # 计算类别统计量
            agg = data.groupby(col)[self.target].agg(['sum', 'count'])
            agg['encoded'] = (agg['sum'] + self.alpha * self.global_mean) / (agg['count'] + self.alpha)
            
            self.encoders[col] = agg['encoded'].to_dict()
    
    def transform(self, X):
        X_transformed = X.copy()
        
        for col in self.columns:
            col_name = f"{col}_Encoded"
            X_transformed[col_name] = X[col].map(self.encoders[col])
            X_transformed[col_name] = X_transformed[col_name].fillna(self.global_mean)
            
        return X_transformed.drop(columns=self.columns)
    
    def fit_transform(self, X, y):
        self.fit(X, y)
        return self.transform(X)

# 使用示例
from sklearn.datasets import fetch_openml

# 加载数据
data = fetch_openml(name='titanic', version=1, as_frame=True)['frame']
data = data[['pclass', 'sex', 'age', 'sibsp', 'parch', 'embarked', 'survived']].dropna()

# 准备数据
X = data.drop('survived', axis=1)
y = data['survived']

# 转换分类变量
categorical_cols = ['pclass', 'sex', 'embarked']
for col in categorical_cols:
    le = LabelEncoder()
    X[col] = le.fit_transform(X[col])

# 拆分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 应用目标均值编码
encoder = TargetMeanEncoder(columns=categorical_cols, target='target')
X_train_encoded = encoder.fit_transform(X_train, y_train)
X_test_encoded = encoder.transform(X_test)

print("编码后的训练数据:")
print(X_train_encoded.head())

何时使用Target Mean Encoding?

  1. 高基数分类特征:当分类变量有大量类别时
  2. 基于树的模型:GBDT、随机森林等
  3. 特征工程:作为特征交叉的基础
  4. 推荐系统:用户ID、物品ID等特征

何时避免使用?

  1. 线性模型:可能导致多重共线性问题
  2. 小数据集:过拟合风险较高
  3. 类别样本量极不平衡:小样本类别的编码可能不可靠

高级技巧

  1. 多目标编码:处理多分类问题

    class MultiTargetEncoder(TargetMeanEncoder):
        def fit(self, X, y):
            # y是多列DataFrame
            pass
    
  2. 添加噪声:防止过拟合

    X_encoded['feature'] += np.random.normal(0, 0.01, size=len(X_encoded))
    
  3. 分层编码:对不同子群体使用不同的编码

    group_encodings = {}
    for group in groups:
        group_data = data[data['group'] == group]
        group_encodings[group] = calc_encoding(group_data)
    

总结

Target Mean Encoding是一种强大的分类变量编码技术,特别适合处理高基数特征和基于树的模型。使用时需注意:

  • 使用平滑处理避免过拟合
  • 通过交叉验证防止目标泄露
  • 处理测试集中的未知类别
  • 考虑与其他编码技术的组合

正确应用Target Mean Encoding可以显著提升模型性能,同时保持特征的简洁性和可解释性。