4. 客户基础特征构建:客户类型、行业、地区、风险等级、开户时长、账户状态等静态特征编码与归一化
客户基础特征是 AML 规则引擎中最稳定、最易获取的一类特征。它们通常来自 KYC(Know Your Customer)数据,在客户生命周期内变化频率低,因此被称为“静态特征”。本章将系统讲解如何对客户类型、行业、地区、风险等级、开户时长、账户状态等核心静态特征进行编码与归一化,使其成为规则模型可用的数值化输入。
4.1 静态特征分类与原始数据形态
在开始特征工程之前,需要明确各类静态特征的原始数据类型。不同数据类型决定了后续编码与归一化的技术路线。
| 特征名称 | 原始数据类型 | 常见取值示例 | 特征工程目标 |
|---|---|---|---|
| 客户类型 | 类别型(无序) | 个人、企业、个体工商户、政府机构 | One-Hot / Label Encoding |
| 行业 | 类别型(有序/无序) | 金融、房地产、珠宝、贸易、科技 | Target Encoding / Frequency Encoding |
| 地区 | 类别型(有序/无序) | 北京、上海、深圳、纽约、伦敦 | One-Hot / 风险等级映射 |
| 风险等级 | 有序类别型 | 低风险、中风险、高风险、极高风险 | Ordinal Encoding |
| 开户时长 | 数值型(连续) | 30天、180天、365天、1095天 | 归一化 / 分箱 |
| 账户状态 | 类别型(有序) | 正常、休眠、冻结、销户 | Ordinal Encoding |
特征工程原则:类别型特征优先考虑业务语义与模型兼容性。对于树模型(如 XGBoost、LightGBM),Label Encoding 通常足够;对于线性模型或神经网络,必须使用 One-Hot Encoding 或 Embedding。
4.2 类别型特征的编码方法
4.2.1 One-Hot Encoding(独热编码)
适用于无序类别型特征,如客户类型、地区。将每个类别扩展为一个独立的二元特征(0/1)。
# Python 示例:使用 pandas 进行 One-Hot Encoding
import pandas as pd
df = pd.DataFrame({
'customer_type': ['个人', '企业', '个体工商户', '个人', '企业'],
'region': ['北京', '上海', '深圳', '北京', '广州']
})
# One-Hot Encoding
df_encoded = pd.get_dummies(df, columns=['customer_type', 'region'], prefix=['type', 'region'])
print(df_encoded.columns.tolist())
# 输出:['type_个人', 'type_企业', 'type_个体工商户', 'region_北京', 'region_上海', 'region_深圳', 'region_广州']
注意维度爆炸:当类别基数较大时(如地区有 300+ 个取值),One-Hot Encoding 会导致特征维度急剧膨胀。此时建议先进行低频类别合并(如将出现次数 < 100 的类别归为“其他”),或改用 Target Encoding。
4.2.2 Ordinal Encoding(有序编码)
适用于有序类别型特征,如风险等级、账户状态。编码时需保持业务顺序,避免模型学到错误的排序关系。
| 风险等级 | 业务顺序 | Ordinal 编码值 |
|---|---|---|
| 低风险 | 1 | 1 |
| 中风险 | 2 | 2 |
| 高风险 | 3 | 3 |
| 极高风险 | 4 | 4 |
# Python 示例:手动映射有序编码
risk_mapping = {'低风险': 1, '中风险': 2, '高风险': 3, '极高风险': 4}
df['risk_level_encoded'] = df['risk_level'].map(risk_mapping)
status_mapping = {'正常': 1, '休眠': 2, '冻结': 3, '销户': 4}
df['account_status_encoded'] = df['account_status'].map(status_mapping)
4.2.3 Target Encoding(目标编码)
适用于高基数类别特征,如行业、地区。用目标变量(如是否涉洗钱)的统计量(均值、中位数)替换原始类别。
# Python 示例:Target Encoding(需配合交叉验证防止过拟合)
from category_encoders import TargetEncoder
encoder = TargetEncoder(cols=['industry', 'region'])
df['industry_target_enc'] = encoder.fit_transform(df['industry'], df['is_aml_flag'])
df['region_target_enc'] = encoder.fit_transform(df['region'], df['is_aml_flag'])
行业特征工程实战建议:AML 场景中,某些行业(如珠宝、房地产、虚拟货币)天然具有更高的洗钱风险。建议在 Target Encoding 基础上叠加“行业风险权重”作为辅助特征,权重由合规专家根据 FATF 指引设定。
4.3 数值型特征的归一化与分箱
4.3.1 开户时长归一化
开户时长(Account Age)是连续数值型特征,取值范围可能从 0 天到数十年。直接输入模型会导致量纲差异,需进行归一化。
| 归一化方法 | 公式 | 适用场景 | 输出范围 |
|---|---|---|---|
| Min-Max 归一化 | (x - min) / (max - min) | 特征值有明确上下界 | [0, 1] |
| Z-Score 标准化 | (x - μ) / σ | 特征值分布近似正态 | 无界(均值 0,标准差 1) |
| 分箱(Binning) | 将连续值离散化为区间 | 需要捕捉非线性效应 | 类别型 |
# Python 示例:开户时长归一化与分箱
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import numpy as np
# 原始数据
account_age_days = np.array([30, 180, 365, 730, 1095, 1825, 3650]).reshape(-1, 1)
# Min-Max 归一化
scaler_minmax = MinMaxScaler()
age_minmax = scaler_minmax.fit_transform(account_age_days)
# Z-Score 标准化
scaler_std = StandardScaler()
age_zscore = scaler_std.fit_transform(account_age_days)
# 分箱(业务含义分箱)
bins = [0, 90, 365, 730, 1825, np.inf]
labels = ['新开户', '短期', '中期', '长期', '老客户']
df['account_age_bin'] = pd.cut(df['account_age_days'], bins=bins, labels=labels)
4.3.2 账户状态与开户时长的交叉特征
单一特征往往不足以刻画风险。将账户状态与开户时长组合,可以构建更有区分度的交叉特征。
4.4 特征归一化的业务考量
在 AML 场景中,归一化不仅仅是数学变换,还需要结合业务含义进行决策。
- Min-Max 归一化:适用于开户时长、交易金额等有明确业务上下界的特征。例如,开户时长上限可设为 3650 天(约 10 年),超过该值的客户统一视为“老客户”。
- Z-Score 标准化:适用于分布相对对称的特征。若开户时长分布严重右偏(大量新客户 + 少量老客户),建议先做对数变换再标准化。
- 分箱(Binning):当特征与目标变量存在非线性关系时(如新开户和极老客户风险较高,中期客户风险较低),分箱可以捕捉这种 U 型关系。
实战案例:某银行 AML 项目中发现,开户时长在 0-30 天的客户涉洗钱概率是 30-365 天客户的 3.2 倍。通过将开户时长分箱为“新开户(0-30天)”、“短期(31-365天)”、“中期(366-1095天)”、“长期(1096天以上)”四个区间,模型 AUC 提升了 0.07。
4.5 特征编码后的维度管理与存储
完成编码与归一化后,需要将特征存储为模型可读取的格式。推荐使用稀疏矩阵存储 One-Hot 编码后的高维特征,以节省内存。
# Python 示例:使用稀疏矩阵存储 One-Hot 编码结果
from scipy.sparse import hstack, csr_matrix
from sklearn.preprocessing import OneHotEncoder
# 原始类别特征
cat_features = df[['customer_type', 'region']].values
# One-Hot 编码(返回稀疏矩阵)
encoder = OneHotEncoder(sparse_output=True, handle_unknown='ignore')
cat_encoded = encoder.fit_transform(cat_features)
# 数值特征(已归一化)
num_features = csr_matrix(df[['account_age_scaled', 'risk_level_encoded']].values)
# 合并为最终特征矩阵
final_features = hstack([cat_encoded, num_features])
print(f"最终特征矩阵形状: {final_features.shape}")
# 输出:最终特征矩阵形状: (100000, 45)
4.6 本章小结
| 特征 | 推荐编码方法 | 归一化/后处理 | 注意事项 |
|---|---|---|---|
| 客户类型 | One-Hot Encoding | 无需归一化 | 类别数少(通常 < 10),直接独热 |
| 行业 | Target Encoding + 行业风险权重 | 无需归一化 | 高基数时合并低频类别 |
| 地区 | One-Hot Encoding 或 风险等级映射 | 无需归一化 | 可结合 FATF 高风险地区列表 |
| 风险等级 | Ordinal Encoding | Min-Max 到 [0,1] | 保持业务顺序,不可逆序 |
| 开户时长 | 分箱 + Ordinal Encoding | Min-Max 或 Z-Score | 注意长尾分布,建议对数变换 |
| 账户状态 | Ordinal Encoding | 无需归一化 | 冻结/销户状态需特殊处理 |