Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TrendPulse

基于 TikTok 信号的趋势预测系统,预测未来 48 小时最可能爆发的内容趋势。

架构说明

三层架构,各层通过接口交互,互不直接依赖。

存储层

使用 BadgerDB + BadgerHold 嵌入式存储,无需外部数据库进程,单二进制部署。Repository interface 抽象了所有存储操作,internal/repository/mongo/internal/repository/postgres/ 目录预留了未来迁移路径。

存储四种实体:

  • Trend:趋势文档,包含名称、类型、类别、地区等元数据
  • Signal:时序指标快照,每小时一条,记录 post 数量、创作者数、播放量、互动量、播放集中度
  • TrendStats:策略的预计算结果,由调度器写入,API 层只读不算
  • CategoryMapping:类别反向索引,绕开 BadgerHold 不支持数组字段索引的限制

计算层

采用可插拔 Strategy 接口设计。每个策略实现 Strategy 接口,接收 SignalReader(懒加载,按需读取信号),输出统一的 TrendStats 结构。

策略通过 Registry 注册,Scheduler 按配置间隔定时触发所有已注册策略并行计算,结果批量写入 StatsRepository。A/B 测试通过 active_strategy 配置切换,历史数据保留。

API 层绝不触发计算,只读取调度器写入的预计算结果。

API 层

使用 Go 标准库 net/http(Go 1.22+ 方法模式路由),RPC 风格统一响应格式:

{"code": 0, "message": "ok", "data": {...}}

五个端点:

方法 路径 说明
GET /trends 趋势列表(分页,支持 ?phase= 过滤)
GET /trends/{id} 趋势详情 + 当前策略统计
GET /trends/rising Top-K 爆发概率最高的趋势
POST /ingest/trends 写入趋势文档
POST /ingest/signals 写入信号数据

评分算法说明及设计思路

设计思路

Score(爆发概率)和 Phase(当前阶段)由同一个 Strategy 共同计算,共享特征提取逻辑。这是有意为之的设计:Phase 的定义(什么样的信号组合 = 爆发期)决定了 Score 要预测的目标,强制分离会产生人为耦合。

不同策略可以定义完全不同的 Phase 语义和 Score 算法,只要输出统一的 TrendStats 结构即可替换。

TikTok 趋势生命周期(4 个阶段)

阶段 英文值 TikTok 行为特征 是否"正在趋势中"
萌芽期 emerging post 数量稳定线性增长,播放量无明显加速,互动率低位平稳
爆发期 rising 少数视频爆火(view_concentration 激增)→ 带动 post 高速增长;播放量二阶加速度显著;点赞/评论/分享激增
高峰期 peaking post 增长率趋近于零(线性平稳);各指标绝对值保持高位;算法持续分发
衰退期 declining post 数量环比下降;播放量、互动量全面负增长

只有 risingpeaking 被视为"正在趋势中",是系统的核心关注对象。

爆发概率评分(Score)

Score 回答:该趋势在未来 48 小时内进入爆发期或高峰期的概率是多少?

sigmoid_v1 参考策略使用加权线性 + Sigmoid 公式:

raw = α × 播放量加速度
    + β × post 增长率
    + γ × 创作者增长率
    + δ × 互动激增率
    + ε × 播放集中度
    − bias

Score = 100 × sigmoid(raw)     sigmoid(x) = 1 / (1 + e^(−x))

各维度说明:

  • 播放量加速度(权重最高):(v[t] - 2·v[t-1] + v[t-2]) / v[t-2],二阶差分,爆发前最早出现的信号
  • post 增长率:创作者跟进速度,反映趋势正在扩散
  • 创作者增长率:有机传播程度,大量新创作者入场是强爆发信号
  • 互动激增率:单内容互动量 / 移动平均,捕捉"一条视频带飞整个趋势"的现象
  • 播放集中度:top-1 视频播放量 / 总播放量,越高说明流量越集中在爆款视频上

所有权重和阈值均在 configs/config.yaml 中配置,无需修改代码即可调参。


本地启动指南

环境要求:Go 1.22+

# 1. 克隆项目并安装依赖
git clone <repo>
cd trendpulse
go mod download

# 2. 启动 API Server(同时启动 Scheduler 定时计算)
go run ./cmd/server
# Server 默认监听 http://localhost:8080

# 3. 另开一个终端,启动交互式模拟器
go run ./cmd/simulator

模拟器启动后显示数据生成计划,按 Enter 逐批发送信号,输入 all 全量发送,输入 q 退出: 模拟器启动后在每批次发送后会自动调用后端接口进行一次聚合计算,模拟真实环境中信号持续流入和定时计算的交互。

注意:评分算法需要至少 9 小时(3 个 3h 聚合窗口)的信号数据才能计算出有意义的 view_acceleration 特征。前几批次 Score 接近 0 是正常的——此时 bias=3.0 远大于可用特征的加权和。建议至少发送 10+ 批次后再查看 /trends/rising

已生成种子数据计划:
  50 个趋势 × 96 批次 (4天 × 24小时)
  阶段分布: 15 viral_spike / 10 slow_burn / 15 steady_emerging / ...

[批次 1/96] t=2026-04-15 00:00 | 按 Enter 发送,'all' 全量,'q' 退出
>
✓ 已发送批次 1: 50 条信号

验证数据

# 查看已摄入的趋势
curl http://localhost:8080/trends | jq .

# 查看爆发概率最高的趋势(Scheduler 运行后可用)
curl "http://localhost:8080/trends/rising?limit=10" | jq .

# 按阶段过滤
curl "http://localhost:8080/trends?phase=rising" | jq .

配置调参:所有超参数(策略权重、调度间隔、信号窗口、模拟器分布)均在 configs/config.yaml 中,修改后重启服务生效。


Seed 数据演示

模拟器默认使用固定 Seed 数据(--seed=true),包含 8 个精心设计的趋势,覆盖所有典型生命周期模式。与随机数据不同,Seed 数据完全确定性,每次运行产生相同的演示效果。

Seed 趋势一览

ID 名称 曲线模式 类别 说明
seed-0001 #AI绘画挑战 晚期爆发 (viral_spike) 科技 前 55 小时平稳,之后指数级爆发(播放量每 4h 翻倍)
seed-0002 #城市骑行日记 持续加速 (steady_emerging) 生活, 健身 从第 1 小时起指数增长(播放量 +8%/h),加速度从一开始就存在
seed-0003 #深夜食堂翻车 早期爆发 (viral_spike) 美食, 搞笑 前 20 小时平稳,之后指数级爆发(+15%/h)
seed-0004 #宿舍健身挑战 缓慢燃烧 (slow_burn) 健身 极缓慢的指数增长(+2.5%/h),晚期可能触及阈值
seed-0005 #电子木鱼 见顶回落 (already_peaking) 搞笑, 科技 高位运行 50 小时(正弦波动),之后指数衰退
seed-0006 #复古胶片风 持续衰退 (declining) 时尚 中等起点,全程下降
seed-0007 #冥想白噪音 完全平稳 (flat) 生活 所有指标恒定,无增长信号
seed-0008 #旅行打卡 极慢线性 (very_slow_burn) 旅行 线性微增,增速不足以触发爆发检测

预期 Rising 时间线

评分算法需要至少 9 小时数据(3 个 3h 聚合窗口)才能计算 view_acceleration 特征。以下是各趋势预期首次出现在 /trends/rising(score ≥ 60)的时间:

批次 趋势 原因
~10 #城市骑行日记 从 h0 起指数增长,播放量加速度持续为正,最先积累足够特征突破阈值
~30 #深夜食堂翻车 h20 起爆后约 9h 积累了 3 个聚合窗口,加速度信号极强
~65 #AI绘画挑战 h55 起爆后约 9h,同样的爆发逻辑但时间更晚
~70+ #宿舍健身挑战 缓慢指数增长的后期,加速度勉强达标,可能在阈值线附近波动
#电子木鱼 始终不会出现:高位但周期波动无净增长,进入衰退后全指标负增长
#复古胶片风 始终不会出现:持续衰退,所有增长指标为负
#冥想白噪音 始终不会出现:完全平稳,无任何增长信号
#旅行打卡 始终不会出现:纯线性增长,二阶导数为零,无法产生加速度

使用方式

# 启动 Server
go run ./cmd/server

# 默认 Seed 模式
go run ./cmd/simulator

# 强制使用随机数据
go run ./cmd/simulator --seed=false

Seed 模式启动后,模拟器会显示完整的趋势列表和预期时间线。按 Enter 逐批发送,建议:

  1. 发送前 10 批 → 检查 /trends/rising 确认 #城市骑行日记 出现
  2. 发送到第 30 批 → 确认 #深夜食堂翻车 加入 Rising 榜
  3. 发送到第 65 批 → 确认 #AI绘画挑战 已上榜
  4. 发送全部 96 批 → 观察最终排名和各趋势阶段变化
# 查看 Rising 榜
curl "http://localhost:8080/trends/rising?limit=10" | jq '.data[] | {name: .trend.name, score: .stats.score, phase: .stats.phase}'

如果时间更充裕,你会如何改进

数据存储升级

BadgerDB 是 MVP 的合理选择,但不支持复杂聚合查询。迁移路径已预留(internal/repository/mongo/internal/repository/postgres/):

  • MongoDB:原生时序集合,聚合管道适合多维度趋势分析
  • PostgreSQL + TimescaleDB:结构化数据 + 时序优化,适合多地区多平台数据关联查询

考虑冷热数据分离:近期数据写入 PostgreSQL,历史数据归档到 BigQuery 进行离线分析和特征挖掘。建立稳定运行的数据归档流程,确保数据安全和查询效率。

实时计算

当前 Scheduler 按固定间隔批量计算(最高延迟 = 调度间隔)。 考虑将计算策略迁移至 Flink/Beam 等流处理框架,实现事件驱动的实时计算,新信号到达即触发相关趋势的增量计算,显著降低从信号摄入到趋势更新的延迟。

策略优化及数据特征维度优化

对于人工设置的维度权重和阈值,可以通过以下方式优化:

  • 根据过去流行事件的数据进行人工校准,调整权重使得历史爆发事件的 Score 更高,非爆发事件的 Score 更低。

当前 sigmoid_v1 的权重是人工设定的。积累足够历史数据后,可以:

  • 训练 Gradient Boosting 分类器预测爆发概率(有监督,标注历史爆发事件)
  • 引入 LSTM / Transformer 捕捉时序模式中的非线性特征
  • 新策略实现 Strategy 接口即可插入,无需修改其他代码
  • 深挖特征工程,挖掘更多潜在的爆发信号维度,尤其是可以参考一些目前对于 Tiktok 本身推荐模式的研究,优化数据收集通路。

可观测性

接入 Prometheus 暴露以下指标,配合 Grafana 可视化:

  • 调度器执行延迟 / 失败率
  • 各策略计算耗时
  • 信号摄入速率 / 积压量

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages