Commit 5b61955e089af78c0d79990d1c6a870576d7b6cf
1 parent
6409ab2c
offline tasks: mem optimize
Showing
9 changed files
with
1273 additions
and
22 deletions
Show diff stats
| @@ -0,0 +1,375 @@ | @@ -0,0 +1,375 @@ | ||
| 1 | +# Swing算法实现总结 | ||
| 2 | + | ||
| 3 | +## 完成的任务 | ||
| 4 | + | ||
| 5 | +本次实现完成了以下功能: | ||
| 6 | + | ||
| 7 | +### 1. Session生成脚本 ✓ | ||
| 8 | + | ||
| 9 | +**文件**: `offline_tasks/scripts/generate_session.py` | ||
| 10 | + | ||
| 11 | +**功能**: | ||
| 12 | +- 从数据库提取用户行为数据 | ||
| 13 | +- 聚合用户session(按商品维度累加权重) | ||
| 14 | +- 支持两种输出格式: | ||
| 15 | + - 标准格式:`uid \t {"item_id":score,...}` | ||
| 16 | + - C++格式:`{"item_id":score,...}` (每行一个用户) | ||
| 17 | + | ||
| 18 | +**主要参数**: | ||
| 19 | +- `--lookback_days`: 回看天数(默认730天) | ||
| 20 | +- `--format`: 输出格式(standard/cpp/both) | ||
| 21 | +- `--output`: 输出文件路径 | ||
| 22 | +- `--debug`: 启用debug模式 | ||
| 23 | + | ||
| 24 | +**使用示例**: | ||
| 25 | +```bash | ||
| 26 | +cd /home/tw/recommendation/offline_tasks | ||
| 27 | +python3 scripts/generate_session.py --lookback_days 730 --format both | ||
| 28 | +``` | ||
| 29 | + | ||
| 30 | +### 2. Swing运行脚本 ✓ | ||
| 31 | + | ||
| 32 | +**文件**: `collaboration/run.sh` | ||
| 33 | + | ||
| 34 | +**改进内容**: | ||
| 35 | +- ✓ 适配新的数据路径(`../offline_tasks/output/`) | ||
| 36 | +- ✓ 自动检测session文件格式(带uid或纯json) | ||
| 37 | +- ✓ 增加配置区域,便于修改参数 | ||
| 38 | +- ✓ 添加错误检查和友好的输出信息 | ||
| 39 | +- ✓ 自动调用debug脚本生成可读文件 | ||
| 40 | +- ✓ 支持自定义Python环境 | ||
| 41 | + | ||
| 42 | +**配置项**: | ||
| 43 | +```bash | ||
| 44 | +SESSION_DATA_DIR="../offline_tasks/output" # session文件目录 | ||
| 45 | +ALPHA=0.7 # Swing alpha参数 | ||
| 46 | +THRESHOLD1=1 # 交互强度阈值1 | ||
| 47 | +THRESHOLD2=3 # 交互强度阈值2 | ||
| 48 | +THREAD_NUM=4 # 线程数 | ||
| 49 | +SHOW_PROGRESS=1 # 显示进度 | ||
| 50 | +PYTHON_CMD="python3" # Python命令 | ||
| 51 | +``` | ||
| 52 | + | ||
| 53 | +**执行流程**: | ||
| 54 | +1. 编译C++程序 | ||
| 55 | +2. 查找session文件 | ||
| 56 | +3. 运行Swing算法(多线程) | ||
| 57 | +4. 合并结果 | ||
| 58 | +5. 生成可读版本(自动调用debug脚本) | ||
| 59 | + | ||
| 60 | +### 3. Debug脚本 ✓ | ||
| 61 | + | ||
| 62 | +**文件**: `offline_tasks/scripts/add_names_to_swing.py` | ||
| 63 | + | ||
| 64 | +**功能**: | ||
| 65 | +- 读取Swing算法输出结果 | ||
| 66 | +- 从数据库获取商品名称映射 | ||
| 67 | +- 生成可读版本:`item_id:name \t similar_id1:name1:score1,...` | ||
| 68 | + | ||
| 69 | +**使用示例**: | ||
| 70 | +```bash | ||
| 71 | +cd /home/tw/recommendation/offline_tasks | ||
| 72 | +python3 scripts/add_names_to_swing.py \ | ||
| 73 | + ../collaboration/output/swing_similar.txt \ | ||
| 74 | + ../collaboration/output/swing_similar_readable.txt \ | ||
| 75 | + --debug | ||
| 76 | +``` | ||
| 77 | + | ||
| 78 | +### 4. 使用文档 ✓ | ||
| 79 | + | ||
| 80 | +**文件**: | ||
| 81 | +- `offline_tasks/SWING_USAGE.md` - 完整使用指南 | ||
| 82 | +- `collaboration/QUICKSTART.md` - 快速开始指南 | ||
| 83 | + | ||
| 84 | +**包含内容**: | ||
| 85 | +- 详细的使用步骤 | ||
| 86 | +- 参数说明和调优建议 | ||
| 87 | +- 故障排查指南 | ||
| 88 | +- 性能优化建议 | ||
| 89 | +- 完整示例 | ||
| 90 | + | ||
| 91 | +## 数据流程 | ||
| 92 | + | ||
| 93 | +``` | ||
| 94 | +┌─────────────────────────────────────────────────────────────┐ | ||
| 95 | +│ 步骤1: 生成Session文件 │ | ||
| 96 | +│ generate_session.py │ | ||
| 97 | +│ ↓ │ | ||
| 98 | +│ 数据库 → 用户行为数据 → 聚合权重 → session.txt.YYYYMMDD │ | ||
| 99 | +└─────────────────────────────────────────────────────────────┘ | ||
| 100 | + ↓ | ||
| 101 | +┌─────────────────────────────────────────────────────────────┐ | ||
| 102 | +│ 步骤2: 运行Swing算法 │ | ||
| 103 | +│ collaboration/run.sh │ | ||
| 104 | +│ ↓ │ | ||
| 105 | +│ session文件 → C++ Swing → sim_matrx.* → swing_similar.txt │ | ||
| 106 | +└─────────────────────────────────────────────────────────────┘ | ||
| 107 | + ↓ | ||
| 108 | +┌─────────────────────────────────────────────────────────────┐ | ||
| 109 | +│ 步骤3: 生成Debug文件 │ | ||
| 110 | +│ add_names_to_swing.py (自动调用) │ | ||
| 111 | +│ ↓ │ | ||
| 112 | +│ swing_similar.txt → 添加商品名 → swing_similar_readable.txt│ | ||
| 113 | +└─────────────────────────────────────────────────────────────┘ | ||
| 114 | +``` | ||
| 115 | + | ||
| 116 | +## 文件格式说明 | ||
| 117 | + | ||
| 118 | +### Session文件格式 | ||
| 119 | + | ||
| 120 | +**标准格式** (`session.txt.YYYYMMDD`): | ||
| 121 | +``` | ||
| 122 | +user_id1 {"123":10.0,"456":5.0,"789":3.0} | ||
| 123 | +user_id2 {"123":8.0,"999":12.0} | ||
| 124 | +``` | ||
| 125 | + | ||
| 126 | +**C++格式** (`session.txt.YYYYMMDD.cpp`): | ||
| 127 | +``` | ||
| 128 | +{"123":10.0,"456":5.0,"789":3.0} | ||
| 129 | +{"123":8.0,"999":12.0} | ||
| 130 | +``` | ||
| 131 | + | ||
| 132 | +### Swing结果格式 | ||
| 133 | + | ||
| 134 | +**原始格式** (`swing_similar.txt`): | ||
| 135 | +``` | ||
| 136 | +12345 67890:0.8523,23456:0.7234,34567:0.6891 | ||
| 137 | +``` | ||
| 138 | + | ||
| 139 | +**可读格式** (`swing_similar_readable.txt`): | ||
| 140 | +``` | ||
| 141 | +12345:iPhone 15 Pro 67890:iPhone 15:0.8523,23456:iPhone 14 Pro:0.7234 | ||
| 142 | +``` | ||
| 143 | + | ||
| 144 | +## 行为权重配置 | ||
| 145 | + | ||
| 146 | +当前配置(在`generate_session.py`中): | ||
| 147 | + | ||
| 148 | +| 行为类型 | 权重 | 说明 | | ||
| 149 | +|---------|------|------| | ||
| 150 | +| purchase | 10.0 | 购买行为(最强信号) | | ||
| 151 | +| contactFactory | 5.0 | 联系厂家 | | ||
| 152 | +| addToCart | 3.0 | 加入购物车 | | ||
| 153 | +| addToPool | 2.0 | 加入询价池 | | ||
| 154 | + | ||
| 155 | +## 快速开始 | ||
| 156 | + | ||
| 157 | +```bash | ||
| 158 | +# 1. 生成session文件(730天数据) | ||
| 159 | +cd /home/tw/recommendation/offline_tasks | ||
| 160 | +python3 scripts/generate_session.py --lookback_days 730 | ||
| 161 | + | ||
| 162 | +# 2. 运行Swing算法 | ||
| 163 | +cd /home/tw/recommendation/collaboration | ||
| 164 | +bash run.sh | ||
| 165 | + | ||
| 166 | +# 3. 查看结果 | ||
| 167 | +cat output/swing_similar_readable.txt | head -20 | ||
| 168 | +``` | ||
| 169 | + | ||
| 170 | +## 项目结构 | ||
| 171 | + | ||
| 172 | +``` | ||
| 173 | +recommendation/ | ||
| 174 | +├── offline_tasks/ | ||
| 175 | +│ ├── scripts/ | ||
| 176 | +│ │ ├── generate_session.py # 新增:生成session | ||
| 177 | +│ │ ├── add_names_to_swing.py # 新增:添加商品名 | ||
| 178 | +│ │ ├── i2i_swing.py # 已有:Python版Swing | ||
| 179 | +│ │ └── debug_utils.py # 已有:Debug工具 | ||
| 180 | +│ ├── config/ | ||
| 181 | +│ │ └── offline_config.py # 配置文件 | ||
| 182 | +│ ├── output/ | ||
| 183 | +│ │ ├── session.txt.YYYYMMDD # 生成的session文件 | ||
| 184 | +│ │ └── session.txt.YYYYMMDD.cpp # C++格式session | ||
| 185 | +│ ├── SWING_USAGE.md # 新增:详细使用文档 | ||
| 186 | +│ └── ... | ||
| 187 | +├── collaboration/ | ||
| 188 | +│ ├── run.sh # 修改:适配新数据格式 | ||
| 189 | +│ ├── QUICKSTART.md # 新增:快速开始 | ||
| 190 | +│ ├── src/ | ||
| 191 | +│ │ ├── swing.cc # C++ Swing实现 | ||
| 192 | +│ │ ├── swing_symmetric.cc # 对称Swing | ||
| 193 | +│ │ ├── icf_simple.cc # 简单协同过滤 | ||
| 194 | +│ │ └── ucf.py # 用户协同 | ||
| 195 | +│ ├── bin/ # 编译后的可执行文件 | ||
| 196 | +│ ├── output_YYYYMMDD/ # 输出目录 | ||
| 197 | +│ │ ├── sim_matrx.* # 多线程输出 | ||
| 198 | +│ │ ├── swing_similar.txt # 合并结果 | ||
| 199 | +│ │ └── swing_similar_readable.txt # 可读结果 | ||
| 200 | +│ └── output -> output_YYYYMMDD # 软链接 | ||
| 201 | +└── SWING_IMPLEMENTATION_SUMMARY.md # 本文档 | ||
| 202 | +``` | ||
| 203 | + | ||
| 204 | +## 参数调优建议 | ||
| 205 | + | ||
| 206 | +### 针对B2B低频场景 | ||
| 207 | + | ||
| 208 | +```bash | ||
| 209 | +# Session生成 | ||
| 210 | +--lookback_days 730 # 2年数据(B2B交互频次低) | ||
| 211 | + | ||
| 212 | +# Swing算法 | ||
| 213 | +ALPHA=0.5-0.7 # 关注用户共同行为的多样性 | ||
| 214 | +THRESHOLD1=1 # 低阈值,保留更多数据 | ||
| 215 | +THRESHOLD2=3 # 中等阈值,过滤噪音 | ||
| 216 | +THREAD_NUM=4-8 # 根据服务器配置 | ||
| 217 | +``` | ||
| 218 | + | ||
| 219 | +### 针对大数据量场景 | ||
| 220 | + | ||
| 221 | +```bash | ||
| 222 | +# 增加线程数 | ||
| 223 | +THREAD_NUM=8 | ||
| 224 | + | ||
| 225 | +# 修改C++代码参数 | ||
| 226 | +max_sim_list_len=300 # 相似列表长度 | ||
| 227 | +max_session_list_len=100 # session截断长度 | ||
| 228 | +``` | ||
| 229 | + | ||
| 230 | +## 与现有系统集成 | ||
| 231 | + | ||
| 232 | +### 1. 定时任务 | ||
| 233 | + | ||
| 234 | +```bash | ||
| 235 | +# 每天凌晨2点运行 | ||
| 236 | +0 2 * * * cd /home/tw/recommendation/offline_tasks && \ | ||
| 237 | + python3 scripts/generate_session.py && \ | ||
| 238 | + cd ../collaboration && bash run.sh | ||
| 239 | +``` | ||
| 240 | + | ||
| 241 | +### 2. 结果导入Redis | ||
| 242 | + | ||
| 243 | +可使用现有的 `load_index_to_redis.py` 脚本导入结果。 | ||
| 244 | + | ||
| 245 | +### 3. 与Python版Swing对比 | ||
| 246 | + | ||
| 247 | +- **C++版本**(本次实现):性能更好,适合大数据量 | ||
| 248 | +- **Python版本**(`i2i_swing.py`):易于调试,支持时间衰减 | ||
| 249 | + | ||
| 250 | +可以运行两个版本对比效果: | ||
| 251 | +```bash | ||
| 252 | +# Python版本 | ||
| 253 | +python3 offline_tasks/scripts/i2i_swing.py --debug | ||
| 254 | + | ||
| 255 | +# C++版本 | ||
| 256 | +cd collaboration && bash run.sh | ||
| 257 | +``` | ||
| 258 | + | ||
| 259 | +## 测试验证 | ||
| 260 | + | ||
| 261 | +### 1. 小数据量测试 | ||
| 262 | + | ||
| 263 | +```bash | ||
| 264 | +# 生成小范围数据(30天) | ||
| 265 | +python3 scripts/generate_session.py --lookback_days 30 | ||
| 266 | + | ||
| 267 | +# 运行Swing | ||
| 268 | +cd ../collaboration | ||
| 269 | +bash run.sh | ||
| 270 | +``` | ||
| 271 | + | ||
| 272 | +### 2. 查看结果质量 | ||
| 273 | + | ||
| 274 | +```bash | ||
| 275 | +# 查看可读版本前100行 | ||
| 276 | +head -100 output/swing_similar_readable.txt | ||
| 277 | + | ||
| 278 | +# 检查相似度分布 | ||
| 279 | +cat output/swing_similar.txt | awk -F'\t' '{print NF-1}' | sort -n | uniq -c | ||
| 280 | +``` | ||
| 281 | + | ||
| 282 | +### 3. 性能测试 | ||
| 283 | + | ||
| 284 | +```bash | ||
| 285 | +# 记录运行时间 | ||
| 286 | +time bash run.sh | ||
| 287 | +``` | ||
| 288 | + | ||
| 289 | +## 故障排查 | ||
| 290 | + | ||
| 291 | +### 常见问题 | ||
| 292 | + | ||
| 293 | +1. **Session文件不存在** | ||
| 294 | + - 先运行 `generate_session.py` | ||
| 295 | + | ||
| 296 | +2. **编译失败** | ||
| 297 | + - 检查g++版本:`g++ --version` | ||
| 298 | + - 手动编译:`cd collaboration && make` | ||
| 299 | + | ||
| 300 | +3. **数据库连接失败** | ||
| 301 | + - 检查配置:`offline_tasks/config/offline_config.py` | ||
| 302 | + - 测试连接:`python3 offline_tasks/test_connection.py` | ||
| 303 | + | ||
| 304 | +4. **结果为空** | ||
| 305 | + - 降低threshold参数 | ||
| 306 | + - 增加lookback_days | ||
| 307 | + - 检查数据量:`wc -l output/session.txt.*` | ||
| 308 | + | ||
| 309 | +详细故障排查参见:`offline_tasks/SWING_USAGE.md` | ||
| 310 | + | ||
| 311 | +## 后续优化方向 | ||
| 312 | + | ||
| 313 | +1. **性能优化** | ||
| 314 | + - 支持分布式计算 | ||
| 315 | + - 增量更新机制 | ||
| 316 | + - 结果缓存 | ||
| 317 | + | ||
| 318 | +2. **功能增强** | ||
| 319 | + - 支持多种相似度算法 | ||
| 320 | + - 在线实时更新 | ||
| 321 | + - A/B测试框架 | ||
| 322 | + | ||
| 323 | +3. **可观测性** | ||
| 324 | + - 添加监控指标 | ||
| 325 | + - 结果质量评估 | ||
| 326 | + - 自动报警 | ||
| 327 | + | ||
| 328 | +## 相关文档 | ||
| 329 | + | ||
| 330 | +- **详细使用指南**: `offline_tasks/SWING_USAGE.md` | ||
| 331 | +- **快速开始**: `collaboration/QUICKSTART.md` | ||
| 332 | +- **配置说明**: `offline_tasks/config/offline_config.py` | ||
| 333 | +- **Debug工具**: `offline_tasks/scripts/debug_utils.py` | ||
| 334 | +- **Swing算法原理**: `collaboration/README.md` | ||
| 335 | + | ||
| 336 | +## 维护说明 | ||
| 337 | + | ||
| 338 | +### 代码维护 | ||
| 339 | + | ||
| 340 | +- **Session生成**: `offline_tasks/scripts/generate_session.py` | ||
| 341 | +- **Swing执行**: `collaboration/run.sh` | ||
| 342 | +- **Debug脚本**: `offline_tasks/scripts/add_names_to_swing.py` | ||
| 343 | + | ||
| 344 | +### 配置维护 | ||
| 345 | + | ||
| 346 | +- **数据库配置**: `offline_tasks/config/offline_config.py` | ||
| 347 | +- **行为权重**: `generate_session.py` 中的 `behavior_weights` | ||
| 348 | +- **Swing参数**: `collaboration/run.sh` 中的配置区域 | ||
| 349 | + | ||
| 350 | +### 日志查看 | ||
| 351 | + | ||
| 352 | +```bash | ||
| 353 | +# Session生成日志 | ||
| 354 | +ls offline_tasks/logs/debug/generate_session_*.log | ||
| 355 | + | ||
| 356 | +# Swing运行日志 | ||
| 357 | +ls collaboration/logs/ | ||
| 358 | +``` | ||
| 359 | + | ||
| 360 | +## 总结 | ||
| 361 | + | ||
| 362 | +本次实现完成了一套完整的C++ Swing算法工作流: | ||
| 363 | + | ||
| 364 | +1. ✓ **前置任务**:Session文件生成(`generate_session.py`) | ||
| 365 | +2. ✓ **核心算法**:C++ Swing执行(改进的`run.sh`) | ||
| 366 | +3. ✓ **后处理**:Debug文件生成(`add_names_to_swing.py`) | ||
| 367 | +4. ✓ **文档完善**:详细使用指南和快速开始 | ||
| 368 | + | ||
| 369 | +所有脚本都支持debug模式,便于调试和监控。整体流程自动化程度高,只需一条命令即可完成全流程。 | ||
| 370 | + | ||
| 371 | +--- | ||
| 372 | + | ||
| 373 | +**实现时间**: 2024-10-17 | ||
| 374 | +**状态**: ✅ 已完成 | ||
| 375 | + |
| @@ -0,0 +1,70 @@ | @@ -0,0 +1,70 @@ | ||
| 1 | +# Swing算法快速开始 | ||
| 2 | + | ||
| 3 | +## 快速运行(3步) | ||
| 4 | + | ||
| 5 | +### 1. 生成Session文件 | ||
| 6 | + | ||
| 7 | +```bash | ||
| 8 | +cd /home/tw/recommendation/offline_tasks | ||
| 9 | +python3 scripts/generate_session.py --lookback_days 730 | ||
| 10 | +``` | ||
| 11 | + | ||
| 12 | +这会生成: | ||
| 13 | +- `output/session.txt.YYYYMMDD` - 标准格式(uid \t json) | ||
| 14 | +- `output/session.txt.YYYYMMDD.cpp` - C++格式(纯json) | ||
| 15 | + | ||
| 16 | +### 2. 运行Swing算法 | ||
| 17 | + | ||
| 18 | +```bash | ||
| 19 | +cd /home/tw/recommendation/collaboration | ||
| 20 | +bash run.sh | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +### 3. 查看结果 | ||
| 24 | + | ||
| 25 | +```bash | ||
| 26 | +# 查看可读版本(带商品名称) | ||
| 27 | +cat output/swing_similar_readable.txt | head -20 | ||
| 28 | + | ||
| 29 | +# 或查看原始版本(仅ID) | ||
| 30 | +cat output/swing_similar.txt | head -20 | ||
| 31 | +``` | ||
| 32 | + | ||
| 33 | +## 输出文件 | ||
| 34 | + | ||
| 35 | +- `output_YYYYMMDD/swing_similar.txt` - Swing相似度结果(ID格式) | ||
| 36 | +- `output_YYYYMMDD/swing_similar_readable.txt` - 可读版本(ID:名称格式) | ||
| 37 | + | ||
| 38 | +## 配置修改 | ||
| 39 | + | ||
| 40 | +如需调整参数,编辑 `run.sh`: | ||
| 41 | + | ||
| 42 | +```bash | ||
| 43 | +# 数据路径 | ||
| 44 | +SESSION_DATA_DIR="../offline_tasks/output" | ||
| 45 | + | ||
| 46 | +# Swing参数 | ||
| 47 | +ALPHA=0.7 # 0.5-1.0,越小越关注用户共同行为 | ||
| 48 | +THRESHOLD1=1 # 1-5,交互强度阈值 | ||
| 49 | +THRESHOLD2=3 # 1-10,相似度计算阈值 | ||
| 50 | +THREAD_NUM=4 # 线程数 | ||
| 51 | +``` | ||
| 52 | + | ||
| 53 | +## 详细文档 | ||
| 54 | + | ||
| 55 | +查看完整文档:`../offline_tasks/SWING_USAGE.md` | ||
| 56 | + | ||
| 57 | +## 常见问题 | ||
| 58 | + | ||
| 59 | +**Q: 如何查看商品名称?** | ||
| 60 | +A: 结果文件 `swing_similar_readable.txt` 已自动添加商品名称 | ||
| 61 | + | ||
| 62 | +**Q: 如何调整相似商品数量?** | ||
| 63 | +A: 修改 `src/swing.cc` 中的 `max_sim_list_len`(默认300) | ||
| 64 | + | ||
| 65 | +**Q: Session文件找不到?** | ||
| 66 | +A: 先运行步骤1生成session文件 | ||
| 67 | + | ||
| 68 | +**Q: 运行时间?** | ||
| 69 | +A: 1万商品约1-5分钟,10万商品约10-30分钟 | ||
| 70 | + |
No preview for this file type
No preview for this file type
No preview for this file type
collaboration/run.sh
| 1 | #!/bin/bash | 1 | #!/bin/bash |
| 2 | -source ~/.bash_profile | 2 | +source ~/.bash_profile |
| 3 | 3 | ||
| 4 | +# ============================================================================ | ||
| 5 | +# 配置区域 - 可根据实际情况修改 | ||
| 6 | +# ============================================================================ | ||
| 7 | + | ||
| 8 | +# 数据路径配置 | ||
| 9 | +# 修改这个路径指向实际的session文件位置 | ||
| 10 | +SESSION_DATA_DIR="../offline_tasks/output" | ||
| 11 | + | ||
| 12 | +# Swing算法参数 | ||
| 13 | +ALPHA=0.7 # Swing算法的alpha参数 | ||
| 14 | +THRESHOLD1=1 # 交互强度阈值1 | ||
| 15 | +THRESHOLD2=3 # 交互强度阈值2 | ||
| 16 | +THREAD_NUM=4 # 线程数 | ||
| 17 | +SHOW_PROGRESS=1 # 是否显示进度 (0/1) | ||
| 18 | + | ||
| 19 | +# Python环境(如果需要特定的Python环境,在这里配置) | ||
| 20 | +PYTHON_CMD="python3" | ||
| 21 | + | ||
| 22 | +# ============================================================================ | ||
| 23 | +# 脚本执行区域 | ||
| 24 | +# ============================================================================ | ||
| 25 | + | ||
| 26 | +# 编译C++程序 | ||
| 27 | +echo "编译Swing程序..." | ||
| 4 | make | 28 | make |
| 29 | +if [[ $? -ne 0 ]]; then | ||
| 30 | + echo "编译失败,退出" | ||
| 31 | + exit 1 | ||
| 32 | +fi | ||
| 33 | + | ||
| 34 | +# 获取日期 | ||
| 35 | +DAY=`date +"%Y%m%d"` | ||
| 36 | +# 如果需要使用特定日期,取消下面的注释 | ||
| 37 | +# DAY=20241017 | ||
| 5 | 38 | ||
| 6 | -DAY=`date -d "1 days ago" +"%Y%m%d"` | ||
| 7 | -# DAY=20240923 | 39 | +echo "处理日期: ${DAY}" |
| 8 | 40 | ||
| 9 | -# 清理当前目录下output_开头的 365天以前创建的目录 | ||
| 10 | -find . -type d -name 'output_*' -ctime +365 -exec rm -rf {} \; | ||
| 11 | -find logs/ -type f -mtime +180 -exec rm -f {} \; | 41 | +# 清理旧的输出目录(365天前)和日志(180天前) |
| 42 | +find . -type d -name 'output_*' -ctime +365 -exec rm -rf {} \; 2>/dev/null | ||
| 43 | +mkdir -p logs | ||
| 44 | +find logs/ -type f -mtime +180 -exec rm -f {} \; 2>/dev/null | ||
| 12 | 45 | ||
| 46 | +# 创建输出目录 | ||
| 13 | output_dir=output_${DAY} | 47 | output_dir=output_${DAY} |
| 14 | -mkdir ${output_dir} | 48 | +mkdir -p ${output_dir} |
| 15 | 49 | ||
| 50 | +# 确定session文件路径 | ||
| 51 | +# 优先使用带日期的文件,如果不存在则使用.cpp格式的文件 | ||
| 52 | +SESSION_FILE="${SESSION_DATA_DIR}/session.txt.${DAY}.cpp" | ||
| 53 | +if [[ ! -f ${SESSION_FILE} ]]; then | ||
| 54 | + SESSION_FILE="${SESSION_DATA_DIR}/session.txt.${DAY}" | ||
| 55 | +fi | ||
| 56 | + | ||
| 57 | +if [[ ! -f ${SESSION_FILE} ]]; then | ||
| 58 | + echo "错误: Session文件不存在: ${SESSION_FILE}" | ||
| 59 | + echo "请先运行 generate_session.py 生成session文件" | ||
| 60 | + exit 1 | ||
| 61 | +fi | ||
| 16 | 62 | ||
| 17 | -# cat ../fetch_data/data/session.txt.${DAY} | bin/swing 0.7 1 3 4 ${output_dir} 1 | ||
| 18 | -cat ../fetch_data/data/session.txt.all | cut -f 2 | bin/swing 0.7 1 3 4 ${output_dir} 1 | 63 | +echo "使用session文件: ${SESSION_FILE}" |
| 64 | +echo "Swing参数: alpha=${ALPHA}, threshold1=${THRESHOLD1}, threshold2=${THRESHOLD2}, threads=${THREAD_NUM}" | ||
| 19 | 65 | ||
| 20 | -# cat ./data/${DAY}/* | bin/swing_symmetric 0.8 1.0 0 | ||
| 21 | -# cat ./data/${DAY}/* | bin/swing_1st_order 0.1 0.5 1 1 | 66 | +# 运行Swing算法 |
| 67 | +# 如果session文件格式是 "uid \t json",需要用cut -f 2提取json部分 | ||
| 68 | +# 如果session文件格式是纯json(每行一个),直接cat即可 | ||
| 69 | +echo "开始运行Swing算法..." | ||
| 70 | +if grep -q $'\t' ${SESSION_FILE}; then | ||
| 71 | + # 包含tab,需要提取第二列 | ||
| 72 | + echo "检测到session文件包含uid,提取json部分..." | ||
| 73 | + cat ${SESSION_FILE} | cut -f 2 | bin/swing ${ALPHA} ${THRESHOLD1} ${THRESHOLD2} ${THREAD_NUM} ${output_dir} ${SHOW_PROGRESS} | ||
| 74 | +else | ||
| 75 | + # 纯json格式 | ||
| 76 | + echo "检测到session文件为纯json格式..." | ||
| 77 | + cat ${SESSION_FILE} | bin/swing ${ALPHA} ${THRESHOLD1} ${THRESHOLD2} ${THREAD_NUM} ${output_dir} ${SHOW_PROGRESS} | ||
| 78 | +fi | ||
| 22 | 79 | ||
| 23 | -# 检查命令是否成功执行 | 80 | +# 检查Swing算法是否成功执行 |
| 24 | if [[ $? -eq 0 ]]; then | 81 | if [[ $? -eq 0 ]]; then |
| 25 | - # 如果成功执行,删除已有的软链接或文件,并创建新的软链接 | 82 | + echo "Swing算法执行成功" |
| 83 | + | ||
| 84 | + # 更新软链接指向最新输出 | ||
| 26 | if [[ -e output ]]; then | 85 | if [[ -e output ]]; then |
| 27 | rm -rf output | 86 | rm -rf output |
| 28 | fi | 87 | fi |
| 29 | ln -s "${output_dir}" output | 88 | ln -s "${output_dir}" output |
| 30 | - echo "命令执行成功,软链接已更新为指向 ${output_dir}" | 89 | + echo "软链接已更新为指向 ${output_dir}" |
| 90 | + | ||
| 91 | + # 合并结果文件 | ||
| 92 | + echo "合并结果文件..." | ||
| 93 | + cat output/sim_matrx.* > output/swing_similar.txt | ||
| 94 | + echo "结果已合并到 output/swing_similar.txt" | ||
| 95 | + | ||
| 96 | + # 生成可读的debug文件(添加商品名称) | ||
| 97 | + echo "生成可读的debug文件..." | ||
| 98 | + DEBUG_SCRIPT="../offline_tasks/scripts/add_names_to_swing.py" | ||
| 99 | + | ||
| 100 | + if [[ -f ${DEBUG_SCRIPT} ]]; then | ||
| 101 | + ${PYTHON_CMD} ${DEBUG_SCRIPT} output/swing_similar.txt output/swing_similar_readable.txt --debug | ||
| 102 | + | ||
| 103 | + if [[ $? -eq 0 ]]; then | ||
| 104 | + echo "Debug文件已生成: output/swing_similar_readable.txt" | ||
| 105 | + else | ||
| 106 | + echo "警告: 生成debug文件失败,但Swing结果已保存" | ||
| 107 | + fi | ||
| 108 | + else | ||
| 109 | + echo "警告: Debug脚本不存在: ${DEBUG_SCRIPT}" | ||
| 110 | + echo "跳过生成可读文件" | ||
| 111 | + fi | ||
| 112 | + | ||
| 31 | else | 113 | else |
| 32 | - echo "命令执行失败,未更新软链接" | 114 | + echo "Swing算法执行失败,未更新软链接" |
| 115 | + exit 1 | ||
| 33 | fi | 116 | fi |
| 34 | 117 | ||
| 35 | -# 对结果进行合并 | ||
| 36 | -cat output/sim_matrx.* > output/swing_similar.txt | 118 | +# ============================================================================ |
| 119 | +# 用户协同过滤(UCF)- 可选 | ||
| 120 | +# ============================================================================ | ||
| 37 | 121 | ||
| 122 | +# 如果需要运行UCF,取消下面的注释 | ||
| 123 | +# echo "运行用户协同过滤..." | ||
| 124 | +# # 仅使用最新的5万条数据 | ||
| 125 | +# tail -n 50000 ${SESSION_FILE} > output/ucf.input | ||
| 126 | +# python3 src/ucf.py output/ucf.input output/ucf.txt | ||
| 38 | 127 | ||
| 39 | -# 用户协同 | ||
| 40 | -# 仅使用最新的10万条数据,降低历史数据的影响,使得给每个user推荐的结果随着最新数据动态变化 | ||
| 41 | -# 2024-10-10 最近几个月平均每天1000,5万大概为50天 | ||
| 42 | -tail -n 50000 ../fetch_data/data/session.txt.all > output/ucf.input | ||
| 43 | -python3 src/ucf.py output/ucf.input output/ucf.txt | 128 | +echo "全部完成!" |
| 129 | +echo "结果文件:" | ||
| 130 | +echo " - Swing相似度: ${output_dir}/swing_similar.txt" | ||
| 131 | +echo " - Swing可读版: ${output_dir}/swing_similar_readable.txt" | ||
| 44 | 132 | ||
| 45 | 133 | ||
| 46 | 134 |
| @@ -0,0 +1,322 @@ | @@ -0,0 +1,322 @@ | ||
| 1 | +# Swing算法使用指南 | ||
| 2 | + | ||
| 3 | +本文档介绍如何使用C++版本的Swing算法进行物品相似度计算。 | ||
| 4 | + | ||
| 5 | +## 目录结构 | ||
| 6 | + | ||
| 7 | +``` | ||
| 8 | +recommendation/ | ||
| 9 | +├── offline_tasks/ | ||
| 10 | +│ ├── scripts/ | ||
| 11 | +│ │ ├── generate_session.py # 生成用户session文件 | ||
| 12 | +│ │ └── add_names_to_swing.py # 给结果添加商品名称 | ||
| 13 | +│ └── output/ | ||
| 14 | +│ └── session.txt.YYYYMMDD # 生成的session文件 | ||
| 15 | +└── collaboration/ | ||
| 16 | + ├── run.sh # Swing算法执行脚本 | ||
| 17 | + ├── src/ | ||
| 18 | + │ ├── swing.cc # Swing算法实现 | ||
| 19 | + │ └── ucf.py # 用户协同过滤 | ||
| 20 | + └── output_YYYYMMDD/ | ||
| 21 | + ├── swing_similar.txt # Swing结果(ID格式) | ||
| 22 | + └── swing_similar_readable.txt # Swing结果(带商品名) | ||
| 23 | +``` | ||
| 24 | + | ||
| 25 | +## 使用流程 | ||
| 26 | + | ||
| 27 | +### 步骤1: 生成Session文件 | ||
| 28 | + | ||
| 29 | +首先需要从数据库提取用户行为数据,生成session文件。 | ||
| 30 | + | ||
| 31 | +```bash | ||
| 32 | +cd /home/tw/recommendation/offline_tasks | ||
| 33 | + | ||
| 34 | +# 基本用法(使用默认参数:730天数据) | ||
| 35 | +python3 scripts/generate_session.py | ||
| 36 | + | ||
| 37 | +# 指定回看天数 | ||
| 38 | +python3 scripts/generate_session.py --lookback_days 365 | ||
| 39 | + | ||
| 40 | +# 启用debug模式查看详细信息 | ||
| 41 | +python3 scripts/generate_session.py --lookback_days 730 --debug | ||
| 42 | + | ||
| 43 | +# 指定输出文件路径 | ||
| 44 | +python3 scripts/generate_session.py --output output/session.txt.20241017 | ||
| 45 | + | ||
| 46 | +# 选择输出格式 | ||
| 47 | +python3 scripts/generate_session.py --format both # 同时生成两种格式(默认) | ||
| 48 | +python3 scripts/generate_session.py --format standard # uid \t json 格式 | ||
| 49 | +python3 scripts/generate_session.py --format cpp # 纯json格式(.cpp后缀) | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +**输出文件格式:** | ||
| 53 | + | ||
| 54 | +- `session.txt.YYYYMMDD` - 标准格式(包含uid): | ||
| 55 | + ``` | ||
| 56 | + uid1 \t {"item_id1":10.0,"item_id2":5.0,"item_id3":3.0} | ||
| 57 | + uid2 \t {"item_id4":15.0,"item_id5":8.0} | ||
| 58 | + ``` | ||
| 59 | + | ||
| 60 | +- `session.txt.YYYYMMDD.cpp` - C++格式(纯json): | ||
| 61 | + ``` | ||
| 62 | + {"item_id1":10.0,"item_id2":5.0,"item_id3":3.0} | ||
| 63 | + {"item_id4":15.0,"item_id5":8.0} | ||
| 64 | + ``` | ||
| 65 | + | ||
| 66 | +**行为权重配置:** | ||
| 67 | +- `purchase`: 10.0(购买) | ||
| 68 | +- `contactFactory`: 5.0(联系厂家) | ||
| 69 | +- `addToCart`: 3.0(加入购物车) | ||
| 70 | +- `addToPool`: 2.0(加入询价池) | ||
| 71 | + | ||
| 72 | +### 步骤2: 运行Swing算法 | ||
| 73 | + | ||
| 74 | +session文件生成后,运行C++版本的Swing算法。 | ||
| 75 | + | ||
| 76 | +```bash | ||
| 77 | +cd /home/tw/recommendation/collaboration | ||
| 78 | + | ||
| 79 | +# 直接运行(使用默认配置) | ||
| 80 | +bash run.sh | ||
| 81 | + | ||
| 82 | +# 或者给脚本添加执行权限后运行 | ||
| 83 | +chmod +x run.sh | ||
| 84 | +./run.sh | ||
| 85 | +``` | ||
| 86 | + | ||
| 87 | +**配置说明(修改run.sh中的参数):** | ||
| 88 | + | ||
| 89 | +```bash | ||
| 90 | +# 数据路径配置 | ||
| 91 | +SESSION_DATA_DIR="../offline_tasks/output" # session文件目录 | ||
| 92 | + | ||
| 93 | +# Swing算法参数 | ||
| 94 | +ALPHA=0.7 # Swing算法的alpha参数(越小越关注用户共同行为) | ||
| 95 | +THRESHOLD1=1 # 交互强度阈值1(用于筛选用户行为) | ||
| 96 | +THRESHOLD2=3 # 交互强度阈值2(用于计算相似度) | ||
| 97 | +THREAD_NUM=4 # 线程数(根据CPU核心数调整) | ||
| 98 | +SHOW_PROGRESS=1 # 是否显示进度 (0/1) | ||
| 99 | + | ||
| 100 | +# Python环境 | ||
| 101 | +PYTHON_CMD="python3" # 如需使用特定Python环境,修改此处 | ||
| 102 | +``` | ||
| 103 | + | ||
| 104 | +**脚本执行流程:** | ||
| 105 | + | ||
| 106 | +1. 编译C++程序(swing, icf_simple, swing_symmetric) | ||
| 107 | +2. 查找当天日期的session文件 | ||
| 108 | +3. 运行Swing算法计算物品相似度 | ||
| 109 | +4. 合并多线程输出结果 | ||
| 110 | +5. 自动调用debug脚本生成可读版本 | ||
| 111 | + | ||
| 112 | +### 步骤3: 查看结果 | ||
| 113 | + | ||
| 114 | +运行完成后,结果文件位于 `collaboration/output_YYYYMMDD/` 目录: | ||
| 115 | + | ||
| 116 | +**1. swing_similar.txt** - 原始结果(ID格式) | ||
| 117 | +``` | ||
| 118 | +12345 \t 67890:0.8523,23456:0.7234,34567:0.6891 | ||
| 119 | +``` | ||
| 120 | +格式:`item_id \t similar_item_id1:score1,similar_item_id2:score2,...` | ||
| 121 | + | ||
| 122 | +**2. swing_similar_readable.txt** - 可读结果(带商品名) | ||
| 123 | +``` | ||
| 124 | +12345:iPhone 15 Pro \t 67890:iPhone 15:0.8523,23456:iPhone 14 Pro:0.7234 | ||
| 125 | +``` | ||
| 126 | +格式:`item_id:item_name \t similar_item_id1:name1:score1,similar_item_id2:name2:score2,...` | ||
| 127 | + | ||
| 128 | +### 步骤4: 单独生成Debug文件(可选) | ||
| 129 | + | ||
| 130 | +如果需要为其他文件生成可读版本: | ||
| 131 | + | ||
| 132 | +```bash | ||
| 133 | +cd /home/tw/recommendation/offline_tasks | ||
| 134 | + | ||
| 135 | +# 基本用法 | ||
| 136 | +python3 scripts/add_names_to_swing.py collaboration/output/swing_similar.txt | ||
| 137 | + | ||
| 138 | +# 指定输出文件 | ||
| 139 | +python3 scripts/add_names_to_swing.py \ | ||
| 140 | + collaboration/output/swing_similar.txt \ | ||
| 141 | + collaboration/output/my_readable.txt | ||
| 142 | + | ||
| 143 | +# 启用debug模式 | ||
| 144 | +python3 scripts/add_names_to_swing.py \ | ||
| 145 | + collaboration/output/swing_similar.txt \ | ||
| 146 | + --debug | ||
| 147 | +``` | ||
| 148 | + | ||
| 149 | +## 参数调优建议 | ||
| 150 | + | ||
| 151 | +### Swing算法参数 | ||
| 152 | + | ||
| 153 | +1. **alpha (0.5-1.0)** | ||
| 154 | + - 越小:越关注用户共同行为的多样性 | ||
| 155 | + - 越大:越容忽略用户重叠度 | ||
| 156 | + - 建议:0.5-0.7(B2B场景) | ||
| 157 | + | ||
| 158 | +2. **threshold1 (1-5)** | ||
| 159 | + - 用于筛选用户的有效行为 | ||
| 160 | + - 建议:1-3(低频场景可用1) | ||
| 161 | + | ||
| 162 | +3. **threshold2 (1-10)** | ||
| 163 | + - 用于计算相似度的行为强度阈值 | ||
| 164 | + - 建议:3-5(需要较强的交互信号) | ||
| 165 | + | ||
| 166 | +4. **thread_num (1-20)** | ||
| 167 | + - 根据CPU核心数设置 | ||
| 168 | + - 建议:4-8(普通服务器) | ||
| 169 | + | ||
| 170 | +### 数据范围参数 | ||
| 171 | + | ||
| 172 | +1. **lookback_days** | ||
| 173 | + - B2B低频场景:建议730天(2年) | ||
| 174 | + - B2C高频场景:建议30-90天 | ||
| 175 | + - 数据量大时可适当减少 | ||
| 176 | + | ||
| 177 | +## 完整示例 | ||
| 178 | + | ||
| 179 | +```bash | ||
| 180 | +# 1. 生成session文件(730天数据) | ||
| 181 | +cd /home/tw/recommendation/offline_tasks | ||
| 182 | +python3 scripts/generate_session.py --lookback_days 730 --debug | ||
| 183 | + | ||
| 184 | +# 2. 检查生成的文件 | ||
| 185 | +ls -lh output/session.txt.* | ||
| 186 | +# 应该看到: | ||
| 187 | +# session.txt.20241017 | ||
| 188 | +# session.txt.20241017.cpp | ||
| 189 | + | ||
| 190 | +# 3. 运行Swing算法 | ||
| 191 | +cd /home/tw/recommendation/collaboration | ||
| 192 | +bash run.sh | ||
| 193 | + | ||
| 194 | +# 4. 查看结果 | ||
| 195 | +ls -lh output/swing_similar* | ||
| 196 | +cat output/swing_similar_readable.txt | head -20 | ||
| 197 | +``` | ||
| 198 | + | ||
| 199 | +## 故障排查 | ||
| 200 | + | ||
| 201 | +### 问题1: Session文件不存在 | ||
| 202 | + | ||
| 203 | +``` | ||
| 204 | +错误: Session文件不存在: ../offline_tasks/output/session.txt.20241017.cpp | ||
| 205 | +``` | ||
| 206 | + | ||
| 207 | +**解决方法:** | ||
| 208 | +```bash | ||
| 209 | +cd /home/tw/recommendation/offline_tasks | ||
| 210 | +python3 scripts/generate_session.py | ||
| 211 | +``` | ||
| 212 | + | ||
| 213 | +### 问题2: 编译失败 | ||
| 214 | + | ||
| 215 | +``` | ||
| 216 | +编译失败,退出 | ||
| 217 | +``` | ||
| 218 | + | ||
| 219 | +**解决方法:** | ||
| 220 | +```bash | ||
| 221 | +cd /home/tw/recommendation/collaboration | ||
| 222 | +# 检查编译器 | ||
| 223 | +g++ --version | ||
| 224 | + | ||
| 225 | +# 手动编译 | ||
| 226 | +make clean | ||
| 227 | +make | ||
| 228 | + | ||
| 229 | +# 检查依赖 | ||
| 230 | +ls include/ | ||
| 231 | +ls utils/ | ||
| 232 | +``` | ||
| 233 | + | ||
| 234 | +### 问题3: 数据库连接失败 | ||
| 235 | + | ||
| 236 | +``` | ||
| 237 | +获取数据失败: Connection refused | ||
| 238 | +``` | ||
| 239 | + | ||
| 240 | +**解决方法:** | ||
| 241 | +- 检查数据库配置:`offline_tasks/config/offline_config.py` | ||
| 242 | +- 测试连接:`python3 offline_tasks/test_connection.py` | ||
| 243 | +- 确认网络和防火墙设置 | ||
| 244 | + | ||
| 245 | +### 问题4: 结果为空 | ||
| 246 | + | ||
| 247 | +**可能原因:** | ||
| 248 | +- threshold1/threshold2设置过高,过滤掉了所有数据 | ||
| 249 | +- 数据量太少,用户和商品交集不足 | ||
| 250 | + | ||
| 251 | +**解决方法:** | ||
| 252 | +- 降低threshold参数(如threshold1=0.5, threshold2=1) | ||
| 253 | +- 增加lookback_days | ||
| 254 | +- 检查数据量:`wc -l output/session.txt.*` | ||
| 255 | + | ||
| 256 | +## 性能优化 | ||
| 257 | + | ||
| 258 | +### 大数据量场景 | ||
| 259 | + | ||
| 260 | +如果数据量很大(>100万用户,>10万商品): | ||
| 261 | + | ||
| 262 | +1. **增加线程数** | ||
| 263 | + ```bash | ||
| 264 | + THREAD_NUM=8 # 或更多 | ||
| 265 | + ``` | ||
| 266 | + | ||
| 267 | +2. **分批处理** | ||
| 268 | + - 可以将session文件按用户分片 | ||
| 269 | + - 分别运行Swing算法 | ||
| 270 | + - 最后合并结果 | ||
| 271 | + | ||
| 272 | +3. **调整max_session_list_len** | ||
| 273 | + - 修改 `src/swing.cc` 中的 `max_session_list_len` | ||
| 274 | + - 限制每个用户的最大行为数 | ||
| 275 | + | ||
| 276 | +### 内存优化 | ||
| 277 | + | ||
| 278 | +如果遇到内存不足: | ||
| 279 | + | ||
| 280 | +1. 减少 `max_sim_list_len`(默认300) | ||
| 281 | +2. 减少 `max_session_list_len`(默认100) | ||
| 282 | +3. 分批处理数据 | ||
| 283 | + | ||
| 284 | +## 集成到定时任务 | ||
| 285 | + | ||
| 286 | +```bash | ||
| 287 | +# 添加到crontab | ||
| 288 | +crontab -e | ||
| 289 | + | ||
| 290 | +# 每天凌晨2点运行 | ||
| 291 | +0 2 * * * cd /home/tw/recommendation/offline_tasks && python3 scripts/generate_session.py && cd ../collaboration && bash run.sh >> logs/swing_$(date +\%Y\%m\%d).log 2>&1 | ||
| 292 | +``` | ||
| 293 | + | ||
| 294 | +## 相关文档 | ||
| 295 | + | ||
| 296 | +- [Swing算法原理](./collaboration/README.md) | ||
| 297 | +- [离线任务配置](./offline_tasks/config/offline_config.py) | ||
| 298 | +- [Debug工具使用](./offline_tasks/scripts/debug_utils.py) | ||
| 299 | + | ||
| 300 | +## 常见问题 | ||
| 301 | + | ||
| 302 | +**Q: session文件格式选择哪个?** | ||
| 303 | +A: run.sh会自动检测格式。建议使用 `--format both` 生成两种格式。 | ||
| 304 | + | ||
| 305 | +**Q: Swing算法运行多久?** | ||
| 306 | +A: 取决于数据量和线程数。通常: | ||
| 307 | +- 1万商品:1-5分钟 | ||
| 308 | +- 10万商品:10-30分钟 | ||
| 309 | +- 数据量大时建议使用多线程 | ||
| 310 | + | ||
| 311 | +**Q: 如何调整相似商品数量?** | ||
| 312 | +A: 修改 `src/swing.cc` 中的 `max_sim_list_len` 参数(默认300)。 | ||
| 313 | + | ||
| 314 | +**Q: 能否使用Python版本的Swing?** | ||
| 315 | +A: 可以,使用 `offline_tasks/scripts/i2i_swing.py`。但C++版本性能更好。 | ||
| 316 | + | ||
| 317 | +## 联系支持 | ||
| 318 | + | ||
| 319 | +如有问题,请参考: | ||
| 320 | +- 项目README: `/home/tw/recommendation/README.md` | ||
| 321 | +- 故障排查: `/home/tw/recommendation/offline_tasks/TROUBLESHOOTING.md` | ||
| 322 | + |
| @@ -0,0 +1,142 @@ | @@ -0,0 +1,142 @@ | ||
| 1 | +""" | ||
| 2 | +给Swing算法输出结果添加name映射 | ||
| 3 | +输入格式: item_id \t similar_item_id1:score1,similar_item_id2:score2,... | ||
| 4 | +输出格式: item_id:name \t similar_item_id1:name1:score1,similar_item_id2:name2:score2,... | ||
| 5 | +""" | ||
| 6 | +import sys | ||
| 7 | +import os | ||
| 8 | +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) | ||
| 9 | + | ||
| 10 | +import argparse | ||
| 11 | +from datetime import datetime | ||
| 12 | +from db_service import create_db_connection | ||
| 13 | +from offline_tasks.config.offline_config import DB_CONFIG | ||
| 14 | +from offline_tasks.scripts.debug_utils import setup_debug_logger, fetch_name_mappings | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +def add_names_to_swing_result(input_file, output_file, name_mappings, logger=None, debug=False): | ||
| 18 | + """ | ||
| 19 | + 给Swing结果添加name映射 | ||
| 20 | + | ||
| 21 | + Args: | ||
| 22 | + input_file: 输入文件路径 | ||
| 23 | + output_file: 输出文件路径 | ||
| 24 | + name_mappings: ID到名称的映射字典 | ||
| 25 | + logger: 日志记录器 | ||
| 26 | + debug: 是否开启debug模式 | ||
| 27 | + """ | ||
| 28 | + if logger: | ||
| 29 | + logger.info(f"处理文件: {input_file}") | ||
| 30 | + logger.info(f"输出到: {output_file}") | ||
| 31 | + | ||
| 32 | + item_names = name_mappings.get('item', {}) | ||
| 33 | + | ||
| 34 | + processed_lines = 0 | ||
| 35 | + skipped_lines = 0 | ||
| 36 | + | ||
| 37 | + with open(input_file, 'r', encoding='utf-8') as fin, \ | ||
| 38 | + open(output_file, 'w', encoding='utf-8') as fout: | ||
| 39 | + | ||
| 40 | + for line in fin: | ||
| 41 | + line = line.strip() | ||
| 42 | + if not line: | ||
| 43 | + continue | ||
| 44 | + | ||
| 45 | + parts = line.split('\t') | ||
| 46 | + if len(parts) != 2: | ||
| 47 | + skipped_lines += 1 | ||
| 48 | + continue | ||
| 49 | + | ||
| 50 | + item_id = parts[0] | ||
| 51 | + sim_items_str = parts[1] | ||
| 52 | + | ||
| 53 | + # 获取item name | ||
| 54 | + item_name = item_names.get(str(item_id), 'Unknown') | ||
| 55 | + | ||
| 56 | + # 处理相似商品列表 | ||
| 57 | + sim_items = [] | ||
| 58 | + for sim_pair in sim_items_str.split(','): | ||
| 59 | + if ':' not in sim_pair: | ||
| 60 | + continue | ||
| 61 | + | ||
| 62 | + sim_id, score = sim_pair.rsplit(':', 1) | ||
| 63 | + sim_name = item_names.get(str(sim_id), 'Unknown') | ||
| 64 | + | ||
| 65 | + # 格式: item_id:name:score | ||
| 66 | + sim_items.append(f"{sim_id}:{sim_name}:{score}") | ||
| 67 | + | ||
| 68 | + # 写入输出 | ||
| 69 | + sim_items_output = ','.join(sim_items) | ||
| 70 | + fout.write(f"{item_id}:{item_name}\t{sim_items_output}\n") | ||
| 71 | + | ||
| 72 | + processed_lines += 1 | ||
| 73 | + | ||
| 74 | + # Debug: 显示进度 | ||
| 75 | + if debug and logger and processed_lines % 1000 == 0: | ||
| 76 | + logger.debug(f"已处理 {processed_lines} 行") | ||
| 77 | + | ||
| 78 | + if logger: | ||
| 79 | + logger.info(f"处理完成:") | ||
| 80 | + logger.info(f" 成功处理: {processed_lines} 行") | ||
| 81 | + logger.info(f" 跳过: {skipped_lines} 行") | ||
| 82 | + | ||
| 83 | + | ||
| 84 | +def main(): | ||
| 85 | + parser = argparse.ArgumentParser(description='Add names to Swing algorithm output') | ||
| 86 | + parser.add_argument('input_file', type=str, | ||
| 87 | + help='Input file path (Swing output)') | ||
| 88 | + parser.add_argument('output_file', type=str, nargs='?', default=None, | ||
| 89 | + help='Output file path (if not specified, will add _readable suffix)') | ||
| 90 | + parser.add_argument('--debug', action='store_true', | ||
| 91 | + help='Enable debug mode with detailed logging') | ||
| 92 | + | ||
| 93 | + args = parser.parse_args() | ||
| 94 | + | ||
| 95 | + # 设置日志 | ||
| 96 | + logger = setup_debug_logger('add_names_to_swing', debug=args.debug) | ||
| 97 | + | ||
| 98 | + # 如果没有指定输出文件,自动生成 | ||
| 99 | + if args.output_file is None: | ||
| 100 | + input_dir = os.path.dirname(args.input_file) | ||
| 101 | + input_basename = os.path.basename(args.input_file) | ||
| 102 | + name_without_ext = os.path.splitext(input_basename)[0] | ||
| 103 | + args.output_file = os.path.join(input_dir, f"{name_without_ext}_readable.txt") | ||
| 104 | + | ||
| 105 | + logger.info(f"输入文件: {args.input_file}") | ||
| 106 | + logger.info(f"输出文件: {args.output_file}") | ||
| 107 | + | ||
| 108 | + # 检查输入文件是否存在 | ||
| 109 | + if not os.path.exists(args.input_file): | ||
| 110 | + logger.error(f"输入文件不存在: {args.input_file}") | ||
| 111 | + return | ||
| 112 | + | ||
| 113 | + # 创建数据库连接 | ||
| 114 | + logger.info("连接数据库...") | ||
| 115 | + engine = create_db_connection( | ||
| 116 | + DB_CONFIG['host'], | ||
| 117 | + DB_CONFIG['port'], | ||
| 118 | + DB_CONFIG['database'], | ||
| 119 | + DB_CONFIG['username'], | ||
| 120 | + DB_CONFIG['password'] | ||
| 121 | + ) | ||
| 122 | + | ||
| 123 | + # 获取名称映射 | ||
| 124 | + logger.info("获取ID到名称的映射...") | ||
| 125 | + name_mappings = fetch_name_mappings(engine, debug=args.debug) | ||
| 126 | + logger.info(f"获取到 {len(name_mappings['item'])} 个商品名称") | ||
| 127 | + | ||
| 128 | + # 处理文件 | ||
| 129 | + add_names_to_swing_result( | ||
| 130 | + args.input_file, | ||
| 131 | + args.output_file, | ||
| 132 | + name_mappings, | ||
| 133 | + logger=logger, | ||
| 134 | + debug=args.debug | ||
| 135 | + ) | ||
| 136 | + | ||
| 137 | + logger.info("完成!") | ||
| 138 | + | ||
| 139 | + | ||
| 140 | +if __name__ == '__main__': | ||
| 141 | + main() | ||
| 142 | + |
| @@ -0,0 +1,254 @@ | @@ -0,0 +1,254 @@ | ||
| 1 | +""" | ||
| 2 | +生成用户行为Session文件 | ||
| 3 | +从数据库读取用户行为,生成适用于C++ Swing算法的session文件 | ||
| 4 | +输出格式: uid \t {"item_id":score,"item_id":score,...} | ||
| 5 | +""" | ||
| 6 | +import sys | ||
| 7 | +import os | ||
| 8 | +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) | ||
| 9 | + | ||
| 10 | +import pandas as pd | ||
| 11 | +import json | ||
| 12 | +from collections import defaultdict | ||
| 13 | +import argparse | ||
| 14 | +from datetime import datetime, timedelta | ||
| 15 | +from db_service import create_db_connection | ||
| 16 | +from offline_tasks.config.offline_config import ( | ||
| 17 | + DB_CONFIG, OUTPUT_DIR, get_time_range, | ||
| 18 | + DEFAULT_LOOKBACK_DAYS | ||
| 19 | +) | ||
| 20 | +from offline_tasks.scripts.debug_utils import setup_debug_logger, log_dataframe_info | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +def aggregate_user_sessions(df, behavior_weights, logger=None, debug=False): | ||
| 24 | + """ | ||
| 25 | + 聚合用户行为session | ||
| 26 | + | ||
| 27 | + Args: | ||
| 28 | + df: DataFrame with columns: user_id, item_id, event_type, create_time | ||
| 29 | + behavior_weights: 行为权重字典 | ||
| 30 | + logger: 日志记录器 | ||
| 31 | + debug: 是否开启debug模式 | ||
| 32 | + | ||
| 33 | + Returns: | ||
| 34 | + Dict[user_id, Dict[item_id, score]] | ||
| 35 | + """ | ||
| 36 | + if logger: | ||
| 37 | + logger.info("开始聚合用户行为session...") | ||
| 38 | + | ||
| 39 | + # 添加权重列 | ||
| 40 | + df['weight'] = df['event_type'].map(behavior_weights).fillna(1.0) | ||
| 41 | + | ||
| 42 | + # 按用户聚合 | ||
| 43 | + user_sessions = defaultdict(lambda: defaultdict(float)) | ||
| 44 | + | ||
| 45 | + for _, row in df.iterrows(): | ||
| 46 | + user_id = row['user_id'] | ||
| 47 | + item_id = row['item_id'] | ||
| 48 | + weight = row['weight'] | ||
| 49 | + | ||
| 50 | + # 累加权重(同一用户对同一商品的多次行为) | ||
| 51 | + user_sessions[user_id][item_id] += weight | ||
| 52 | + | ||
| 53 | + if logger: | ||
| 54 | + logger.info(f"聚合完成,共 {len(user_sessions)} 个用户") | ||
| 55 | + | ||
| 56 | + # 统计 | ||
| 57 | + total_interactions = sum(len(items) for items in user_sessions.values()) | ||
| 58 | + avg_interactions = total_interactions / len(user_sessions) if user_sessions else 0 | ||
| 59 | + logger.info(f"平均每个用户交互 {avg_interactions:.2f} 个商品") | ||
| 60 | + | ||
| 61 | + if debug: | ||
| 62 | + # 展示示例 | ||
| 63 | + sample_users = list(user_sessions.items())[:3] | ||
| 64 | + for user_id, items in sample_users: | ||
| 65 | + logger.debug(f"用户 {user_id} 的session: {dict(list(items.items())[:5])}...") | ||
| 66 | + | ||
| 67 | + return user_sessions | ||
| 68 | + | ||
| 69 | + | ||
| 70 | +def save_session_file(user_sessions, output_file, logger=None, debug=False): | ||
| 71 | + """ | ||
| 72 | + 保存session文件 | ||
| 73 | + | ||
| 74 | + 格式: uid \t {"item_id":score,"item_id":score,...} | ||
| 75 | + 其中items按score降序排列 | ||
| 76 | + | ||
| 77 | + Args: | ||
| 78 | + user_sessions: Dict[user_id, Dict[item_id, score]] | ||
| 79 | + output_file: 输出文件路径 | ||
| 80 | + logger: 日志记录器 | ||
| 81 | + debug: 是否开启debug模式 | ||
| 82 | + """ | ||
| 83 | + if logger: | ||
| 84 | + logger.info(f"保存session文件到: {output_file}") | ||
| 85 | + | ||
| 86 | + with open(output_file, 'w', encoding='utf-8') as f: | ||
| 87 | + for user_id, items in user_sessions.items(): | ||
| 88 | + # 按分数降序排序 | ||
| 89 | + sorted_items = sorted(items.items(), key=lambda x: -x[1]) | ||
| 90 | + | ||
| 91 | + # 构建JSON字符串(注意item_id需要加引号) | ||
| 92 | + items_dict = {str(item_id): round(score, 4) for item_id, score in sorted_items} | ||
| 93 | + items_json = json.dumps(items_dict, ensure_ascii=False, separators=(',', ':')) | ||
| 94 | + | ||
| 95 | + # 写入文件 | ||
| 96 | + f.write(f"{user_id}\t{items_json}\n") | ||
| 97 | + | ||
| 98 | + if logger: | ||
| 99 | + logger.info(f"保存完成,共 {len(user_sessions)} 个用户session") | ||
| 100 | + | ||
| 101 | + | ||
| 102 | +def save_session_file_for_cpp(user_sessions, output_file, logger=None, debug=False): | ||
| 103 | + """ | ||
| 104 | + 保存session文件(C++版本格式,不包含uid) | ||
| 105 | + | ||
| 106 | + 格式: {"item_id":score,"item_id":score,...} | ||
| 107 | + 每行一个用户的session,按score降序排列 | ||
| 108 | + | ||
| 109 | + Args: | ||
| 110 | + user_sessions: Dict[user_id, Dict[item_id, score]] | ||
| 111 | + output_file: 输出文件路径 | ||
| 112 | + logger: 日志记录器 | ||
| 113 | + debug: 是否开启debug模式 | ||
| 114 | + """ | ||
| 115 | + if logger: | ||
| 116 | + logger.info(f"保存session文件(C++格式)到: {output_file}") | ||
| 117 | + | ||
| 118 | + with open(output_file, 'w', encoding='utf-8') as f: | ||
| 119 | + for user_id, items in user_sessions.items(): | ||
| 120 | + # 按分数降序排序 | ||
| 121 | + sorted_items = sorted(items.items(), key=lambda x: -x[1]) | ||
| 122 | + | ||
| 123 | + # 构建JSON字符串(注意item_id需要加引号) | ||
| 124 | + items_dict = {f'"{item_id}"': round(score, 4) for item_id, score in sorted_items} | ||
| 125 | + # 手动构建JSON格式(保证引号格式) | ||
| 126 | + items_str = ','.join([f'"{k.strip(chr(34))}":{v}' for k, v in items_dict.items()]) | ||
| 127 | + items_json = '{' + items_str + '}' | ||
| 128 | + | ||
| 129 | + # 写入文件 | ||
| 130 | + f.write(f"{items_json}\n") | ||
| 131 | + | ||
| 132 | + if logger: | ||
| 133 | + logger.info(f"保存完成(C++格式),共 {len(user_sessions)} 个用户session") | ||
| 134 | + | ||
| 135 | + | ||
| 136 | +def main(): | ||
| 137 | + parser = argparse.ArgumentParser(description='Generate user behavior session file') | ||
| 138 | + parser.add_argument('--lookback_days', type=int, default=DEFAULT_LOOKBACK_DAYS, | ||
| 139 | + help=f'Number of days to look back for user behavior (default: {DEFAULT_LOOKBACK_DAYS})') | ||
| 140 | + parser.add_argument('--output', type=str, default=None, | ||
| 141 | + help='Output file path') | ||
| 142 | + parser.add_argument('--debug', action='store_true', | ||
| 143 | + help='Enable debug mode with detailed logging') | ||
| 144 | + parser.add_argument('--format', type=str, default='both', choices=['standard', 'cpp', 'both'], | ||
| 145 | + help='Output format: standard (uid+json), cpp (json only), both (default: both)') | ||
| 146 | + | ||
| 147 | + args = parser.parse_args() | ||
| 148 | + | ||
| 149 | + # 设置日志 | ||
| 150 | + logger = setup_debug_logger('generate_session', debug=args.debug) | ||
| 151 | + | ||
| 152 | + # 记录参数 | ||
| 153 | + logger.info(f"参数配置:") | ||
| 154 | + logger.info(f" lookback_days: {args.lookback_days}") | ||
| 155 | + logger.info(f" debug: {args.debug}") | ||
| 156 | + logger.info(f" format: {args.format}") | ||
| 157 | + | ||
| 158 | + # 创建数据库连接 | ||
| 159 | + logger.info("连接数据库...") | ||
| 160 | + engine = create_db_connection( | ||
| 161 | + DB_CONFIG['host'], | ||
| 162 | + DB_CONFIG['port'], | ||
| 163 | + DB_CONFIG['database'], | ||
| 164 | + DB_CONFIG['username'], | ||
| 165 | + DB_CONFIG['password'] | ||
| 166 | + ) | ||
| 167 | + | ||
| 168 | + # 获取时间范围 | ||
| 169 | + start_date, end_date = get_time_range(args.lookback_days) | ||
| 170 | + logger.info(f"获取数据: {start_date} 到 {end_date}") | ||
| 171 | + | ||
| 172 | + # SQL查询 - 获取用户行为数据 | ||
| 173 | + sql_query = f""" | ||
| 174 | + SELECT | ||
| 175 | + se.anonymous_id AS user_id, | ||
| 176 | + se.item_id, | ||
| 177 | + se.event AS event_type, | ||
| 178 | + se.create_time | ||
| 179 | + FROM | ||
| 180 | + sensors_events se | ||
| 181 | + WHERE | ||
| 182 | + se.event IN ('contactFactory', 'addToPool', 'addToCart', 'purchase') | ||
| 183 | + AND se.create_time >= '{start_date}' | ||
| 184 | + AND se.create_time <= '{end_date}' | ||
| 185 | + AND se.item_id IS NOT NULL | ||
| 186 | + AND se.anonymous_id IS NOT NULL | ||
| 187 | + ORDER BY | ||
| 188 | + se.create_time | ||
| 189 | + """ | ||
| 190 | + | ||
| 191 | + try: | ||
| 192 | + logger.info("执行SQL查询...") | ||
| 193 | + df = pd.read_sql(sql_query, engine) | ||
| 194 | + logger.info(f"获取到 {len(df)} 条记录") | ||
| 195 | + | ||
| 196 | + # Debug: 显示数据详情 | ||
| 197 | + if args.debug: | ||
| 198 | + log_dataframe_info(logger, df, "用户行为数据", sample_size=10) | ||
| 199 | + except Exception as e: | ||
| 200 | + logger.error(f"获取数据失败: {e}") | ||
| 201 | + return | ||
| 202 | + | ||
| 203 | + if len(df) == 0: | ||
| 204 | + logger.warning("没有找到数据") | ||
| 205 | + return | ||
| 206 | + | ||
| 207 | + # 转换create_time为datetime | ||
| 208 | + df['create_time'] = pd.to_datetime(df['create_time']) | ||
| 209 | + | ||
| 210 | + # 定义行为权重 | ||
| 211 | + behavior_weights = { | ||
| 212 | + 'contactFactory': 5.0, | ||
| 213 | + 'addToPool': 2.0, | ||
| 214 | + 'addToCart': 3.0, | ||
| 215 | + 'purchase': 10.0 | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + if logger and args.debug: | ||
| 219 | + logger.debug(f"行为类型分布:") | ||
| 220 | + event_counts = df['event_type'].value_counts() | ||
| 221 | + for event, count in event_counts.items(): | ||
| 222 | + logger.debug(f" {event}: {count} ({count/len(df)*100:.2f}%)") | ||
| 223 | + | ||
| 224 | + # 聚合用户session | ||
| 225 | + user_sessions = aggregate_user_sessions( | ||
| 226 | + df, | ||
| 227 | + behavior_weights, | ||
| 228 | + logger=logger, | ||
| 229 | + debug=args.debug | ||
| 230 | + ) | ||
| 231 | + | ||
| 232 | + # 生成输出文件名 | ||
| 233 | + date_str = datetime.now().strftime("%Y%m%d") | ||
| 234 | + | ||
| 235 | + if args.output: | ||
| 236 | + output_base = args.output | ||
| 237 | + else: | ||
| 238 | + output_base = os.path.join(OUTPUT_DIR, f'session.txt.{date_str}') | ||
| 239 | + | ||
| 240 | + # 保存文件 | ||
| 241 | + if args.format in ['standard', 'both']: | ||
| 242 | + output_file = output_base | ||
| 243 | + save_session_file(user_sessions, output_file, logger=logger, debug=args.debug) | ||
| 244 | + | ||
| 245 | + if args.format in ['cpp', 'both']: | ||
| 246 | + output_file_cpp = output_base + '.cpp' | ||
| 247 | + save_session_file_for_cpp(user_sessions, output_file_cpp, logger=logger, debug=args.debug) | ||
| 248 | + | ||
| 249 | + logger.info("完成!") | ||
| 250 | + | ||
| 251 | + | ||
| 252 | +if __name__ == '__main__': | ||
| 253 | + main() | ||
| 254 | + |