4. 基础特征层构建:用户画像静态特征
基础特征层是风控特征分层架构中的第一层,也是最稳定的数据底座。本层主要从用户注册表、登录表等离线宽表中提取不随时间快速变化的静态属性,形成用户画像的核心标签。这些特征在模型生命周期内通常不会频繁更新,因此适合作为样本清洗和后续衍生特征计算的锚点。
核心原则:静态特征提取必须保证“一人一档”的原子性,避免因多源数据时间戳不一致导致画像污染。建议以用户注册时间作为时间基准,对齐所有静态字段。
4.1 静态特征域与数据源映射
在风控场景中,静态特征主要来源于用户注册信息与首次登录行为。下表列出了典型的数据源与对应特征域:
| 数据源 | 特征域 | 典型字段 | 提取方式 |
|---|---|---|---|
| 用户注册表 | 人口属性 | 年龄、性别、国籍、职业 | 直接映射或规则转换 |
| 用户注册表 | 渠道属性 | 注册渠道、邀请码、推广来源 | 字典编码或One-Hot |
| 登录表 | 设备属性 | 首次登录设备型号、操作系统 | 取最早登录记录 |
| 登录表 | 网络属性 | 首次登录IP、运营商、地区 | IP解析库关联 |
| 用户注册表 | 账户属性 | 注册时间、账户等级、实名状态 | 时间戳截断与状态枚举 |
4.2 基于Spark的静态特征提取流程
使用Spark SQL对用户注册表和登录表进行ETL,核心步骤包括:数据去重、时间对齐、字段转换、画像聚合。以下为典型实现代码:
// 1. 读取用户注册表,去重取最新记录
val regDF = spark.read.table("ods.user_register")
.filter(col("dt") === maxPartition)
.dropDuplicates("user_id")
.select(
col("user_id"),
col("age"),
col("gender"),
col("register_channel"),
col("register_time"),
col("real_name_status")
)
// 2. 读取登录表,取每个用户首次登录记录
val loginDF = spark.read.table("ods.user_login")
.filter(col("dt") === maxPartition)
.groupBy("user_id")
.agg(
first("device_model").as("first_device_model"),
first("os_type").as("first_os_type"),
first("login_ip").as("first_login_ip"),
min("login_time").as("first_login_time")
)
// 3. 关联IP解析库,提取地区与运营商
val ipGeoDF = spark.read.parquet("/data/dim/ip_geo")
val loginWithGeoDF = loginDF
.join(ipGeoDF, loginDF("first_login_ip") === ipGeoDF("ip_start"), "left")
.select(
loginDF("user_id"),
loginDF("first_device_model"),
loginDF("first_os_type"),
ipGeoDF("province").as("first_login_province"),
ipGeoDF("isp").as("first_login_isp")
)
// 4. 合并注册与登录特征,形成用户画像宽表
val userProfileDF = regDF
.join(loginWithGeoDF, Seq("user_id"), "left")
.withColumn("age_group", when(col("age") < 18, "underage")
.when(col("age").between(18, 25), "young")
.when(col("age").between(26, 40), "middle")
.otherwise("senior"))
.withColumn("register_hour", hour(col("register_time")))
// 5. 写入基础特征层
userProfileDF.write
.mode("overwrite")
.partitionBy("dt")
.parquet("/data/feature_layer/base/user_profile/")
工程建议:年龄字段常存在异常值(如0、200),建议在提取时加入
age BETWEEN 1 AND 120的过滤条件,并将异常值标记为-1,避免模型学习到错误分布。
4.3 静态特征质量校验规则
在写入特征层之前,必须执行质量校验。以下为推荐校验清单:
- 完整性校验:用户画像表记录数不应低于注册表用户数的95%,缺失率超过5%的字段需标记并回填默认值。
- 唯一性校验:user_id应为主键,不允许出现重复记录;若存在重复,取register_time最新的一条。
- 值域校验:性别字段仅允许0/1/2(未知/男/女);注册渠道需在预定义字典内;年龄需在1-120之间。
- 时间一致性校验:first_login_time不应早于register_time,若出现则取register_time作为首次登录时间。
4.4 用户画像静态特征分层架构
下图展示了从原始数据源到基础特征层的完整分层架构,以及各层之间的数据流向:
4.5 静态特征在样本清洗中的应用
基础特征层构建完成后,可直接用于样本清洗阶段的异常样本过滤与样本分层:
- 年龄异常过滤:将年龄为0或大于120的用户标记为“年龄异常”,在样本清洗时剔除或降权。
- 渠道黑名单过滤:若注册渠道属于已知作弊渠道(如批量注册工具),直接剔除该用户所有样本。
- 地域冷启动分层:对于首次登录IP归属地为高风险地区的用户,在样本分层时划入“高关注”组,单独训练风控模型。
- 设备指纹去重:若多个用户共享同一首次登录设备型号且注册时间接近,判定为团伙注册,清洗时合并为单一样本。
避坑提示:静态特征一旦写入基础特征层,不应频繁修改。若业务需要更新(如用户修改实名信息),必须通过增量更新方式写入,并保留历史版本快照,避免破坏样本时间序列的一致性。
4.6 静态特征存储与分区策略
为提升下游查询效率,建议采用以下存储与分区方案:
| 存储格式 | 压缩方式 | 分区字段 | 分桶字段 | 更新频率 |
|---|---|---|---|---|
| Parquet | Snappy | dt(天分区) | user_id(128桶) | 每日全量覆盖 |
| Parquet | Zstd | dt + register_channel | user_id(256桶) | 每日增量合并 |
推荐使用每日全量覆盖策略,因为静态特征数据量较小(通常为千万级),全量写入可避免增量合并带来的数据一致性问题。若用户量超过10亿,可切换为增量合并模式,但需额外维护变更日志表。