1. 数据预处理:机器学习项目的隐形基石
在金融风控领域摸爬滚打多年,我见过太多团队把90%的精力花在模型调参上,结果因为数据质量问题导致整个项目翻车。上周刚处理过一个典型案例:某银行反欺诈模型AUC突然从0.85跌到0.6,追查后发现是新接入数据源的收入字段单位从"万元"变成了"元",而预处理流程没有做单位标准化。
数据预处理就像做菜前的食材处理——再厉害的厨师也没法用发霉的食材做出美味。根据我的经验,一个完整的机器学习项目中:
- 数据清洗占40%时间:处理缺失值、异常值就像给食材去泥沙挑杂质
- 特征工程占30%时间:好比根据菜系特点切割搭配食材
- 模型训练其实只占20%:相当于最后的烹饪环节
- 剩下10%是部署监控:类似上菜后的口味跟踪
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据清洗实战:从脏数据到干净样本
2.1 缺失值处理的三种武器
最近处理的一个电商用户数据集让我深刻体会到缺失值处理的复杂性。某个包含200万条记录的数据集中:
- 年龄字段缺失15%:采用随机森林预测填充
- 收入字段缺失30%:使用中位数填充(右偏分布)
- 职业字段缺失5%:标记为"未知"类别
python复制# 实战中的分层处理策略
def handle_missing(df):
# 数值型特征
num_cols = ['age', 'income']
num_imputer = IterativeImputer(max_iter=10)
df[num_cols] = num_imputer.fit_transform(df[num_cols])
# 类别型特征
cat_cols = ['occupation', 'education']
df[cat_cols] = df[cat_cols].fillna('UNKNOWN')
# 时间型特征
time_cols = ['last_login']
df[time_cols] = df[time_cols].fillna(df['register_time'])
return df
关键经验:当缺失率超过40%时,建议直接删除该特征。我曾经在医疗数据中遇到某个化验指标缺失率达60%,硬要填充反而会引入噪声。
2.2 异常值检测的立体防御
去年做信用卡欺诈检测时,我们发现某些"正常交易"的金额异常高(单笔超过500万)。通过组合三种检测方法锁定问题:
- 统计方法:箱线图显示金额>100万为异常
- 业务规则:该银行单笔限额本应是50万
- 聚类分析:DBSCAN将大额交易识别为离群点
python复制# 综合异常检测方案
def detect_outliers(df):
# 统计方法
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1
df['outlier_stats'] = (df['amount'] > Q3 + 3*IQR) | (df['amount'] < Q1 - 3*IQR)
# 业务规则
df['outlier_biz'] = df['amount'] > 500000
# 聚类分析
scaler = StandardScaler()
amounts = scaler.fit_transform(df[['amount']])
clustering = DBSCAN(eps=0.5, min_samples=10).fit(amounts)
df['outlier_cluster'] = clustering.labels_ == -1
return df
3. 数据标准化:让特征站在同一起跑线
3.1 数值标准化的艺术
在最近的风控模型中,我们对比了不同标准化方法对逻辑回归的影响:
| 方法 | 公式 | 适用场景 | 我们的选择理由 |
|---|---|---|---|
| Z-score | (x-μ)/σ | 近似正态分布 | 年龄字段符合正态 |
| Robust | (x-median)/IQR | 存在异常值 | 收入字段右偏严重 |
| Log | log(1+x) | 右偏分布 | 交易金额跨度大 |
| Box-Cox | 复杂变换 | 任意分布 | 最终未采用(计算成本高) |
python复制# 差异化标准化实践
from sklearn.preprocessing import RobustScaler
numeric_features = ['age', 'income', 'transaction_amount']
preprocessor = ColumnTransformer(
transformers=[
('zscore', StandardScaler(), ['age']),
('robust', RobustScaler(), ['income']),
('log', FunctionTransformer(np.log1p), ['transaction_amount'])
])
df[numeric_features] = preprocessor.fit_transform(df[numeric_features])
3.2 类别编码的密码本
处理用户地址数据时,我们创造性地组合了多种编码方式:
- 独热编码:用于省份(32个类别)
- 目标编码:用于城市(300+个类别)
- 二进制编码:用于街道(5000+个类别)
python复制# 混合编码策略
class HybridEncoder(BaseEstimator, TransformerMixin):
def __init__(self, threshold=50):
self.threshold = threshold
def fit(self, X, y=None):
self.encoders = {}
for col in X.columns:
n_unique = X[col].nunique()
if n_unique <= 2:
self.encoders[col] = LabelEncoder()
elif n_unique <= self.threshold:
self.encoders[col] = OneHotEncoder(handle_unknown='ignore')
else:
self.encoders[col] = TargetEncoder()
return self
def transform(self, X):
return pd.concat([
pd.DataFrame(enc.transform(X[[col]]),
columns=[f"{col}_{i}" for i in range(len(enc.categories_[0]))])
if isinstance(enc, OneHotEncoder)
else pd.Series(enc.transform(X[col]), name=col)
for col, enc in self.encoders.items()
], axis=1)
4. 特征工程:从数据到洞察
4.1 时序特征挖掘实例
在用户行为分析中,我们开发了一套时间窗口特征生成器:
python复制def create_time_features(df, user_id, dt_col, value_col):
df[dt_col] = pd.to_datetime(df[dt_col])
features = []
for uid, group in df.groupby(user_id):
group = group.sort_values(dt_col)
# 滑动窗口统计
for window in [7, 30, 90]: # 天为单位
