Target Mean Encoding(目标均值编码)是一种用于处理分类变量的编码技术,它利用目标变量的信息为每个类别创建数值表示。与One-Hot Encoding不同,Target Mean Encoding将每个类别替换为该类别对应的目标变量的平均值。
核心思想
对于每个类别:
类别编码值 = 该类别中目标变量的平均值
例如,在二分类问题中:
类别编码值 = 该类别中正例的比例
为什么使用Target Mean Encoding?
- 高效处理高基数特征:对于具有大量类别的特征(如城市、用户ID),One-Hot Encoding会导致维度灾难
- 保留目标信息:编码值直接反映类别与目标变量的关系
- 改善模型性能:对于基于树的模型(如梯度提升树),通常能带来更好的性能
- 减少维度:生成单个数值特征而非多个虚拟变量
基本实现(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
最佳实践
- 始终使用交叉验证方案:防止目标泄露
- 平滑处理:特别是对于小样本类别
- 处理未知类别:为测试集中的新类别分配全局平均值
- 结合其他编码技术:对于线性模型,可结合使用One-Hot Encoding
- 正则化:在树模型中使用正则化防止过拟合
完整实现示例
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?
- 高基数分类特征:当分类变量有大量类别时
- 基于树的模型:GBDT、随机森林等
- 特征工程:作为特征交叉的基础
- 推荐系统:用户ID、物品ID等特征
何时避免使用?
- 线性模型:可能导致多重共线性问题
- 小数据集:过拟合风险较高
- 类别样本量极不平衡:小样本类别的编码可能不可靠
高级技巧
-
多目标编码:处理多分类问题
class MultiTargetEncoder(TargetMeanEncoder): def fit(self, X, y): # y是多列DataFrame pass -
添加噪声:防止过拟合
X_encoded['feature'] += np.random.normal(0, 0.01, size=len(X_encoded)) -
分层编码:对不同子群体使用不同的编码
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可以显著提升模型性能,同时保持特征的简洁性和可解释性。