逻辑回归实战(一):用户流失预测数据集设计 ------ KingbaseES 存储标签数据
------别让"模糊的业务定义",毁掉你精心训练的分类模型
大家好,我是那个总在模型上线前被问"流失到底怎么算?"、又在 KES 表里翻查用户最后活跃时间的老架构。今天不讲算法,也不调参数------我们解决 AI 落地中一个最基础、却最容易被忽视的问题:
标签(Label)怎么定义?
很多人以为:"流失就是没登录嘛,打个 0/1 就行。"
但现实是:标签定义不清,模型再准也是空中楼阁 。
你预测的是"30 天未登录"?还是"取消订阅"?或是"客服投诉后离网"?不同定义,特征工程、评估指标、业务价值天差地别。
而真相是:标签不是技术问题,而是业务契约。
今天我们就以电信/互联网行业经典的"用户流失预测"为例,手把手设计一套面向逻辑回归的 KingbaseES 数据模型 ,明确标签口径、构建特征快照、隔离时间穿越,并用 Java 完成数据入库。全程基于电科金仓 KES,只为打造一个干净、可解释、可审计的二分类数据底座。
一、业务定义先行:什么是"流失"?
在动手建表前,必须和业务方对齐:
"我们将'流失'定义为:用户在过去 90 天内有活跃行为,但在未来 30 天内未产生任何有效交互(如登录、交易、使用核心功能)。"
这个定义包含三个关键点:
- ✅ 观察窗口:过去 90 天(用于提取特征);
- ✅ 预测窗口:未来 30 天(用于打标签);
- ✅ 有效交互:需明确定义(避免"心跳包"干扰)。
💡 这就是时间锚点(Time Anchor)思维------所有特征和标签都相对于某个固定时间点计算。
二、表结构设计:三层架构防"时间穿越"
我们采用分层设计,确保训练时看不到未来信息:
raw/ → 原始行为日志(事实表)
features/ → 特征快照(按 anchor_date 切片)
labels/ → 标签表(仅含 user_id + anchor_date + is_churn)
datasets/ → 合并后的训练集(带版本)
Step 1:原始行为表(ai_raw.user_events)
sql
CREATE SCHEMA IF NOT EXISTS ai_raw;
CREATE TABLE ai_raw.user_events (
event_id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
event_type VARCHAR(32) NOT NULL, -- 'login', 'payment', 'view'
event_time TIMESTAMP NOT NULL,
amount REAL, -- 交易金额(可选)
device_type VARCHAR(16) -- 'mobile', 'web'
);
-- 建议对 (user_id, event_time) 建索引
CREATE INDEX idx_user_events_time ON ai_raw.user_events (user_id, event_time);
⚠️ 注意:此表只记录事实,不做任何聚合。
Step 2:标签表(ai_labels.user_churn_labels)
sql
CREATE SCHEMA IF NOT EXISTS ai_labels;
CREATE TABLE ai_labels.user_churn_labels (
user_id BIGINT NOT NULL,
anchor_date DATE NOT NULL, -- 特征计算截止日
is_churn BOOLEAN NOT NULL, -- 未来30天是否流失
PRIMARY KEY (user_id, anchor_date)
);
COMMENT ON COLUMN ai_labels.user_churn_labels.anchor_date
IS '特征观察窗口结束日,标签基于 [anchor_date+1, anchor_date+30] 计算';
标签生成逻辑(用 SQL 实现,可调度):
sql
-- 为指定 anchor_date 生成标签
WITH active_users AS (
-- 过去90天有活跃的用户
SELECT DISTINCT user_id
FROM ai_raw.user_events
WHERE event_time >= DATE '2025-03-01' - INTERVAL '90 days'
AND event_time < DATE '2025-03-01'
),
future_activity AS (
-- 未来30天是否有活跃
SELECT DISTINCT user_id
FROM ai_raw.user_events
WHERE event_time >= DATE '2025-03-01'
AND event_time < DATE '2025-03-01' + INTERVAL '30 days'
)
INSERT INTO ai_labels.user_churn_labels (user_id, anchor_date, is_churn)
SELECT
a.user_id,
DATE '2025-03-01',
CASE WHEN f.user_id IS NULL THEN TRUE ELSE FALSE END
FROM active_users a
LEFT JOIN future_activity f ON a.user_id = f.user_id;
✅ 这样,标签完全由历史行为决定,无未来信息泄露。
Step 3:特征快照表(ai_features.user_behavior_snapshot)
sql
CREATE SCHEMA IF NOT EXISTS ai_features;
CREATE TABLE ai_features.user_behavior_snapshot (
user_id BIGINT NOT NULL,
anchor_date DATE NOT NULL,
login_count_7d INT,
login_count_30d INT,
payment_count_30d INT,
total_amount_30d REAL,
last_login_days INT, -- 距 anchor_date 的天数
complaint_flag BOOLEAN,
plan_type VARCHAR(16), -- 'premium', 'basic'
PRIMARY KEY (user_id, anchor_date)
);
特征可通过物化视图或 ETL 任务生成,例如:
sql
-- 示例:计算最近7天登录次数
SELECT
user_id,
COUNT(*) AS login_count_7d
FROM ai_raw.user_events
WHERE event_type = 'login'
AND event_time >= anchor_date - INTERVAL '7 days'
AND event_time < anchor_date
GROUP BY user_id;
Step 4:训练集视图(带版本控制)
sql
CREATE SCHEMA IF NOT EXISTS ai_datasets;
CREATE OR REPLACE VIEW ai_datasets.churn_train_v1 AS
SELECT
f.*,
l.is_churn::INT AS label -- 转为 0/1
FROM ai_features.user_behavior_snapshot f
JOIN ai_labels.user_churn_labels l
ON f.user_id = l.user_id
AND f.anchor_date = l.anchor_date
WHERE f.anchor_date BETWEEN '2024-01-01' AND '2024-12-01';
-- 测试集用 2025 年数据
CREATE OR REPLACE VIEW ai_datasets.churn_test_v1 AS
SELECT ... WHERE anchor_date BETWEEN '2025-01-01' AND '2025-06-01';
同时记录元数据:
sql
CREATE TABLE ai_datasets.dataset_version_meta (
version TEXT PRIMARY KEY,
description TEXT,
train_start DATE,
train_end DATE,
test_start DATE,
test_end DATE,
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO ai_datasets.dataset_version_meta VALUES (
'churn_v1',
'User churn prediction with 90-day behavior window',
'2024-01-01', '2024-12-01',
'2025-01-01', '2025-06-01'
);
三、Java 实现:从日志到标签入库
虽然标签可用 SQL 生成,但复杂逻辑仍需 Java 控制:
java
public void generateChurnLabels(Connection conn, LocalDate anchorDate) throws SQLException {
String sql = """
WITH active_users AS (...),
future_activity AS (...)
INSERT INTO ai_labels.user_churn_labels (user_id, anchor_date, is_churn)
SELECT a.user_id, ?,
CASE WHEN f.user_id IS NULL THEN TRUE ELSE FALSE END
FROM active_users a
LEFT JOIN future_activity f ON a.user_id = f.user_id
""";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setDate(1, java.sql.Date.valueOf(anchorDate));
int count = ps.executeUpdate();
System.out.println("Generated " + count + " labels for " + anchorDate);
}
}
特征快照也可用批量插入:
java
public void insertFeatureSnapshot(Connection conn, List<UserFeature> features) throws SQLException {
String sql = """
INSERT INTO ai_features.user_behavior_snapshot (
user_id, anchor_date, login_count_7d, ..., plan_type
) VALUES (?, ?, ?, ..., ?)
""";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (UserFeature f : features) {
ps.setLong(1, f.userId);
ps.setDate(2, Date.valueOf(f.anchorDate));
ps.setInt(3, f.loginCount7d);
// ... set other fields
ps.addBatch();
}
ps.executeBatch();
}
}
🔗 所有操作依赖 电科金仓 JDBC 驱动,确保
setDate、setBoolean等类型安全。
四、为什么这套设计值得借鉴?
- 时间隔离:anchor_date 明确划分过去与未来;
- 可复现:每个版本对应固定时间窗口;
- 可解释 :业务人员可直接查
is_churn验证逻辑; - 可扩展:新增特征只需改 snapshot 表,不影响标签;
- 国产友好:完全基于 KES 能力,无需外部调度器。
结语:标签,是 AI 与业务的"契约"
在国产化 AI 落地中,我们常被要求"快速出模型"。
但真正的专业,体现在愿意花时间把标签定义清楚。
当你能在电科金仓的 KES 中,用清晰的时间锚点、可审计的 SQL 逻辑、版本化的训练集,构建一个干净的流失预测数据集------你就已经赢了 80% 的团队。
因为接下来,无论是逻辑回归、XGBoost,还是深度学习,它们面对的,将是一个值得信赖的世界。
------ 一位相信"好的标签,胜过千行代码"的架构师