使用 PyTorch 完成一个序列分类任务:模拟用户在一段时间内(如 30 天)的使用行为,然后预测该用户是否会“流失”(churn)。这与之前仅仅预测数值(回归)或对简单文本序列进行分类的例子都不同,可帮助你进一步理解 LSTM 在多特征时间序列分类中的应用。
案例:基于用户行为时间序列的流失预测(Churn Detection)
一、案例背景与思路
-
我们模拟1000 个用户,每个用户在 30 天 内的使用行为,包括:
- daily_usage_time:每天使用APP的时长(小时),
- daily_access_count:每天登录/访问的次数,
- daily_spend:每天在APP内的消费金额。
-
以上三项指标形成一个 (seq_len=30, feature_dim=3) 的时间序列,我们希望根据其整体走势来预测该用户在 30 天后是否“流失”(churn = 1)或“继续活跃”(churn = 0)。
-
模型结构:Many-to-One,即序列输入,单标签输出(流失/不流失)。
-
为了让模型有一定可学习的模式,我们在数据模拟时,假设:
- 如果用户在后半程(第15~30天)的使用时长 & 消费金额 普遍较低,则更可能流失(label=1)。
- 反之,如果后半程使用行为还不错,则倾向于不流失(label=0)。
这是一个简单的“人为设定”模拟逻辑,真实情况下需要你对业务场景有深入了解后再决定如何标注“流失”。
二、环境需求
- Python 3.7+
- PyTorch、Numpy、Matplotlib 等
在本地或 Google Colab 环境均可运行。
三、数据准备
3.1 数据生成
- 样本数量:1000(用户数)
- 时间序列长度:30
- 特征维度:3
下面的函数 generate_user_data
会随机生成用户每天的三项行为数据,并基于设定的条件来标注是否流失。
import numpy as np
import torch
import matplotlib.pyplot as plt
import random
def generate_user_data(num_users=1000, seq_len=30, seed=42):
"""
生成用户在 seq_len 天内的使用行为数据:
- daily_usage_time (随机 0~5小时)
- daily_access_count (随机 0~10次)
- daily_spend (随机 0~50元)
根据后半段使用情况标注是否流失(1)或留存(0)。
"""
np.random.seed(seed)
random.seed(seed)
X = [] # shape: (num_users, seq_len, feature_dim=3)
Y = [] # shape: (num_users,)
for _ in range(num_users):
# 生成随机的时长、访问次数和消费金额
usage_time = np.random.uniform(0, 5, seq_len) # [0, 5) 小时
access_count = np.random.randint(0, 10, seq_len) # [0, 10) 次
spend = np.random.uniform(0, 50, seq_len) # [0, 50) 元
features = np.stack([usage_time, access_count, spend], axis=-1) # (seq_len, 3)
# 简单逻辑:如果后半程(第15-30天)的 usage_time & spend 过低,则流失,否则留存
mean_usage_later = usage_time[15:].mean()
mean_spend_later = spend[15:].mean()
# 设定一个经验阈值,比如时长 < 2小时 & 消费 < 10元 就可能流失
if mean_usage_later < 2 and mean_spend_later < 10:
label = 1 # 流失
else:
label = 0 # 留存
X.append(features)
Y.append(label)
X = np.array(X)
Y = np.array(Y)
return X, Y
# 生成数据
X_all, Y_all = generate_user_data(num_users=1000, seq_len=30)
print("X_all shape:", X_all.shape) # (1000, 30, 3)
print("Y_all shape:", Y_all.shape) # (1000,)
3.2 训练集与测试集划分
# 8:2 划分
train_ratio = 0.8
train_size = int(len(X_all) * train_ratio)
train_X = X_all[:train_size]
train_Y = Y_all[:train_size]
test_X = X_all[train_size:]
test_Y = Y_all[train_size:]
print("Train X:", train_X.shape, "Train Y:", train_Y.shape)
print("Test X:", test_X.shape, "Test Y:", test_Y.shape)
3.3 转成 PyTorch Dataset
我们将数据封装到 Dataset 里,以便后续使用 DataLoader
进行批量训练。
from torch.utils.data import Dataset, DataLoader
class UserBehaviorDataset(Dataset):
def __init__(self, X, Y):
super().__init__()
self.X = X
self.Y = Y
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
x_seq = torch.tensor(self.X[idx], dtype=torch.float32) # shape: (30, 3)
y_label = torch.tensor(self.Y[idx], dtype=torch.long) # 单个标签(0/1),分类任务用 long
return x_seq, y_label
train_dataset = UserBehaviorDataset(train_X, train_Y)
test_dataset = UserBehaviorDataset(test_X, test_Y)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
四、模型构建:LSTM 分类
相比前面预测数值的回归任务,这里是二分类(流失 / 不流失),所以我们的输出层会有 2 个神经元,并使用交叉熵损失(CrossEntropy)。
import torch
import torch.nn as nn
class LSTMChurnClassifier(nn.Module):
def __init__(self, input_dim=3, hidden_size=32, num_layers=1, num_classes=2):
super(LSTMChurnClassifier, self).__init__()
# LSTM 层
self.lstm = nn.LSTM(
input_size=input_dim,
hidden_size=hidden_size,
num_layers=num_layers,
batch_first=True # (batch, seq_len, feature_dim)
)
# 输出层:将最后的隐藏状态映射到 2 个类别
self.fc = nn.Linear(hidden_size, num_classes)
def forward(self, x):
# x: (batch, seq_len=30, input_dim=3)
out, (h_n, c_n) = self.lstm(x)
# 取最后时间步的隐藏状态
last_out = out[:, -1, :] # (batch, hidden_size)
# 全连接分类
logits = self.fc(last_out) # (batch, num_classes=2)
return logits
五、训练模型
5.1 实例化并定义优化器
model = LSTMChurnClassifier(input_dim=3, hidden_size=32, num_layers=1, num_classes=2)
criterion = nn.CrossEntropyLoss() # 二分类可用
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
5.2 训练循环
- 损失函数:
CrossEntropyLoss
- 优化器:Adam
- 评价指标:本示例在训练过程中主要观察 loss,后面会在测试集上计算准确率。
num_epochs = 10
train_losses = []
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for batch_x, batch_y in train_loader:
# 前向计算
logits = model(batch_x) # (batch, 2)
loss = criterion(logits, batch_y) # batch_y shape=(batch,)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
avg_loss = epoch_loss / len(train_loader)
train_losses.append(avg_loss)
if (epoch+1) % 2 == 0:
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")
5.3 训练曲线可视化
plt.figure(figsize=(8,4))
plt.plot(train_losses, label='Train Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Curve')
plt.legend()
plt.show()
六、模型评估与预测
6.1 在测试集上计算准确率
model.eval()
correct = 0
total = 0
with torch.no_grad():
for batch_x, batch_y in test_loader:
logits = model(batch_x)
preds = torch.argmax(logits, dim=1) # (batch,)
correct += (preds == batch_y).sum().item()
total += batch_y.size(0)
acc = correct / total
print(f"Test Accuracy: {acc*100:.2f}%")
由于模拟数据中我们人为设定了后半程使用行为与流失的关联关系,如果训练和模型结构合适,我们应该能得到**显著高于随机水平(50%)**的准确率。
6.2 预测示例
我们可以随机抽取一些测试样本做预测,查看模型输出的类别。
import random
model.eval()
sample_indices = random.sample(range(len(test_dataset)), 5)
print("Random 5 test users:")
for idx in sample_indices:
x_seq, true_label = test_dataset[idx]
with torch.no_grad():
logits = model(x_seq.unsqueeze(0)) # 增加batch维度
pred_label = torch.argmax(logits, dim=1).item()
print(f" - User idx {idx}: True={true_label}, Predict={pred_label}")
七、改进与扩展思路
- 多层 LSTM / 加入 Dropout
- 如果数据更复杂,可将
num_layers
设大于 1,并在 LSTM 层中使用dropout
参数,防止过拟合。
- 如果数据更复杂,可将
- 增加更多特征
- 现实中可以采集更多维度(如点击次数、互动行为、用户属性等)来提升模型判别能力。
- 平衡样本
- 若实际数据中“流失”与“未流失”分布严重不平衡,可考虑加权损失或其他方式处理不均衡问题。
- 更复杂的判定方式
- 本示例只根据“后半程平均值”来生成标签,实际业务中可结合更多条件或打分机制来确定流失定义。
- 其他模型
- 如果序列很长,且对捕捉长距离依赖要求高,可考虑 GRU、Transformer 或 LSTM+Attention 等更高级结构。
八、小结
本案例展示了一个全新的时间序列分类场景:通过多维度用户行为序列,来预测用户流失与否。与之前的示例(如数值回归、文本分类或 sin 波预测)不同的是:
- 我们在数据模拟阶段加入了多特征(usage_time, access_count, spend),并基于后半程表现决定流失标签。
- 整体流程仍遵循 PyTorch 典型套路:Dataset & DataLoader -> LSTM 模型 -> 训练循环 -> 评估。
- 输出维度为 2(二分类),使用
nn.CrossEntropyLoss
进行训练。
在实际业务中,你可以:
- 采集更丰富的时间序列特征(如设备信息、用户画像),
- 定义更合理的“流失”标签规则,
- 使用更先进的门控或注意力模型来提升预测性能,
- 或者考虑更复杂的序列长度(如 90 天以上)。
希望这个全新案例能让你对LSTM 在多特征时间序列分类领域的应用有更直观的理解和操作经验。祝学习顺利,项目成功!
【哈佛博后带小白玩转机器学习】 哔哩哔哩_bilibili
总课时超400+,时长75+小时