8b2590c5b5
- 完成Phase 11: predictV3算法优化研究文档,涵盖6个优化方向的技术分析 - 实现置信度评估功能,提供历史命中率、得分分布、多维度一致性置信度指标 - 扩展回测指标体系,新增NDCG@K、MRR、命中率分布等排名质量评估指标 - 优化转移概率算法,引入二阶马尔可夫链和多属性联合转移增强预测准确性 - 设计权重训练机制,支持网格搜索和遗传算法进行数据驱动的参数优化 - 集成组合特征挖掘功能,采用关联规则和序列模式发现号码间潜在关联 - 实现完整的前端交互界面,支持预测结果显示、置信度展示和回测验证功能 - 建立性能优化策略,包括预计算缓存、批量计算和降级策略保障响应速度
328 lines
11 KiB
Markdown
328 lines
11 KiB
Markdown
---
|
||
phase: 11-predictv3
|
||
plan: 01
|
||
type: execute
|
||
wave: 1
|
||
depends_on: []
|
||
files_modified:
|
||
- application/admin/model/History.php
|
||
autonomous: true
|
||
requirements:
|
||
- PRED-02
|
||
- PRED-05
|
||
must_haves:
|
||
truths:
|
||
- "用户可以在回测结果中看到 NDCG@5 指标"
|
||
- "用户可以在回测结果中看到 MRR 指标"
|
||
- "用户可以看到各排名位置的命中分布统计"
|
||
- "系统在数据不足时返回合理的默认值或提示"
|
||
artifacts:
|
||
- path: "application/admin/model/History.php"
|
||
provides: "NDCG、MRR、命中分布计算方法"
|
||
contains: "_calculateNDCG|_calculateMRR|_calculateHitDistribution"
|
||
key_links:
|
||
- from: "_runBacktestV3"
|
||
to: "_calculateNDCG, _calculateMRR, _calculateHitDistribution"
|
||
via: "method call in return statement"
|
||
---
|
||
|
||
# Phase 11 - Plan 01: 回测指标扩展
|
||
|
||
## Objective
|
||
|
||
扩展 `_runBacktestV3` 方法的回测指标,新增 NDCG@5、MRR、命中率分布等排名质量评估指标,提升算法评估能力。
|
||
|
||
**Purpose:** 当前回测仅返回命中率(Top5)和平均排名,缺少排名质量评估指标。NDCG、MRR 是成熟的推荐系统评估指标,能更全面反映预测排名质量。
|
||
|
||
**Output:** `History.php` 中新增 3 个计算方法,`_runBacktestV3` 返回结果扩展。
|
||
|
||
## Tasks
|
||
|
||
### Task 1: 实现 NDCG@5 计算(含空预测保护和公式文档)
|
||
|
||
<read_first>
|
||
- D:\code\php\amlhc\application\admin\model\History.php (line 3495-3560, _runBacktestV3 方法)
|
||
</read_first>
|
||
|
||
<action>
|
||
在 `History.php` 文件末尾(类内)新增 `_calculateNDCG` 方法:
|
||
|
||
```php
|
||
/**
|
||
* 计算 NDCG@K (Normalized Discounted Cumulative Gain)
|
||
*
|
||
* 公式说明:
|
||
* - DCG (Discounted Cumulative Gain) = Σ(rel_i / log2(rank_i + 1))
|
||
* 其中 rel_i = 1 (命中) 或 0 (未命中),rank_i 为预测排名位置
|
||
* - IDCG (Ideal DCG) = Σ(1 / log2(i + 1)) for i = 1..min(hits, K)
|
||
* 即理想情况下所有命中的号码都排在最前面的DCG值
|
||
* - NDCG = DCG / IDCG,范围 0-1,越接近1表示排名质量越好
|
||
*
|
||
* @param array $backtestDetails 回测详情数组,每项包含 {hit: bool, rank: int}
|
||
* @param int $K Top-K 参数,默认5,评估前K个预测位置的排名质量
|
||
* @return float NDCG值 (0-1范围),空数据时返回0
|
||
*/
|
||
private function _calculateNDCG($backtestDetails, $K = 5)
|
||
{
|
||
// 边缘情况处理:空预测或无效参数
|
||
if (empty($backtestDetails) || $K <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
$dcg = 0;
|
||
$idcg = 0;
|
||
|
||
// 计算 DCG: 命中号码的排名折损累积值
|
||
foreach ($backtestDetails as $detail) {
|
||
if (!isset($detail['hit']) || !isset($detail['rank'])) {
|
||
continue; // 跳过无效数据
|
||
}
|
||
if ($detail['hit'] && $detail['rank'] > 0 && $detail['rank'] <= $K) {
|
||
// DCG公式: rel / log2(rank + 1),命中时 rel=1
|
||
$dcg += 1 / log($detail['rank'] + 1, 2);
|
||
}
|
||
}
|
||
|
||
// 计算 IDCG: 最理想情况下所有命中的 DCG(假设都排在第1位)
|
||
$hitCount = 0;
|
||
foreach ($backtestDetails as $detail) {
|
||
if (isset($detail['hit']) && $detail['hit']) {
|
||
$hitCount++;
|
||
}
|
||
}
|
||
|
||
for ($i = 1; $i <= min($hitCount, $K); $i++) {
|
||
$idcg += 1 / log($i + 1, 2);
|
||
}
|
||
|
||
// 返回标准化值,IDCG为0时返回0避免除零错误
|
||
return $idcg > 0 ? round($dcg / $idcg, 4) : 0;
|
||
}
|
||
```
|
||
|
||
实现要点:
|
||
- 公式:DCG = Σ(1/log2(rank+1)),IDCG = Σ(1/log2(i+1)) for i=1..hits
|
||
- 添加空预测保护:检查 $backtestDetails 是否为空
|
||
- 添加数据完整性检查:确保 hit 和 rank 字段存在
|
||
- 使用 log(rank + 1, 2) 作为折损函数,排名越靠前权重越高
|
||
- 返回 0-1 范围的标准化值,越接近 1 表示排名质量越好
|
||
</action>
|
||
|
||
<acceptance_criteria>
|
||
- grep 正则匹配: `_calculateNDCG\s*\(` 在 History.php 中存在
|
||
- grep 匹配: `empty($backtestDetails)` 在方法中存在(空预测保护)
|
||
- 方法返回 float 类型值
|
||
- 包含函数级注释说明 NDCG 计算逻辑和公式
|
||
</acceptance_criteria>
|
||
|
||
### Task 2: 实现 MRR 和命中分布计算(含边缘情况处理)
|
||
|
||
<read_first>
|
||
- D:\code\php\amlhc\application\admin\model\History.php (新增的 _calculateNDCG 方法位置)
|
||
</read_first>
|
||
|
||
<action>
|
||
在 `_calculateNDCG` 方法后继续新增 `_calculateMRR` 和 `_calculateHitDistribution` 方法:
|
||
|
||
```php
|
||
/**
|
||
* 计算 MRR (Mean Reciprocal Rank)
|
||
* 平均倒数排名,关注命中号码的具体排名位置
|
||
*
|
||
* 公式说明:
|
||
* - MRR = Σ(1/rank_i) / N,其中 rank_i 为命中号码的排名,N 为测试总数
|
||
* - 未命中的测试项贡献 0 到倒数排名
|
||
* - MRR 范围 0-1,越接近1表示命中号码平均排名越靠前
|
||
*
|
||
* @param array $backtestDetails 回测详情数组,每项包含 {hit: bool, rank: int}
|
||
* @return float MRR值 (0-1范围),空数据时返回0
|
||
*/
|
||
private function _calculateMRR($backtestDetails)
|
||
{
|
||
// 边缘情况处理:空预测
|
||
if (empty($backtestDetails)) {
|
||
return 0;
|
||
}
|
||
|
||
$reciprocalRanks = [];
|
||
|
||
foreach ($backtestDetails as $detail) {
|
||
if (!isset($detail['hit']) || !isset($detail['rank'])) {
|
||
continue; // 跳过无效数据
|
||
}
|
||
if ($detail['hit'] && $detail['rank'] > 0) {
|
||
$reciprocalRanks[] = 1 / $detail['rank'];
|
||
} else {
|
||
$reciprocalRanks[] = 0; // 未命中记为0
|
||
}
|
||
}
|
||
|
||
return count($reciprocalRanks) > 0
|
||
? round(array_sum($reciprocalRanks) / count($reciprocalRanks), 4)
|
||
: 0;
|
||
}
|
||
|
||
/**
|
||
* 计算命中率分布
|
||
* 统计各排名位置(1-5)的命中次数分布
|
||
*
|
||
* 结构定义:
|
||
* - 返回格式: {rank_1: n, rank_2: n, rank_3: n, rank_4: n, rank_5: n}
|
||
* - rank_N 表示预测排名第N位的命中次数
|
||
* - 用于前端柱状图可视化展示
|
||
*
|
||
* @param array $backtestDetails 回测详情数组,每项包含 {hit: bool, rank: int}
|
||
* @return array 各排名(1-5)的命中次数统计,键名为 rank_1 到 rank_5
|
||
*/
|
||
private function _calculateHitDistribution($backtestDetails)
|
||
{
|
||
// 边缘情况处理:空预测返回全0分布
|
||
if (empty($backtestDetails)) {
|
||
return [
|
||
'rank_1' => 0,
|
||
'rank_2' => 0,
|
||
'rank_3' => 0,
|
||
'rank_4' => 0,
|
||
'rank_5' => 0
|
||
];
|
||
}
|
||
|
||
// 初始化分布数组,键名使用 rank_N 格式便于前端解析
|
||
$distribution = [
|
||
'rank_1' => 0,
|
||
'rank_2' => 0,
|
||
'rank_3' => 0,
|
||
'rank_4' => 0,
|
||
'rank_5' => 0
|
||
];
|
||
|
||
foreach ($backtestDetails as $detail) {
|
||
if (!isset($detail['hit']) || !isset($detail['rank'])) {
|
||
continue; // 跳过无效数据
|
||
}
|
||
if ($detail['hit'] && $detail['rank'] >= 1 && $detail['rank'] <= 5) {
|
||
$key = 'rank_' . $detail['rank'];
|
||
$distribution[$key]++;
|
||
}
|
||
}
|
||
|
||
return $distribution;
|
||
}
|
||
```
|
||
|
||
实现要点:
|
||
- MRR: 命中号码排名倒数平均值,公式 Σ(1/rank)/N
|
||
- 命中分布: 明确结构为 `{rank_1: n, rank_2: n, ..., rank_5: n}`
|
||
- 两个方法均添加空预测保护和无效数据跳过逻辑
|
||
- hit_distribution 使用 rank_N 键名格式,便于前端柱状图渲染
|
||
</action>
|
||
|
||
<acceptance_criteria>
|
||
- grep 正则匹配: `_calculateMRR\s*\(` 在 History.php 中存在
|
||
- grep 正则匹配: `_calculateHitDistribution\s*\(` 在 History.php 中存在
|
||
- grep 匹配: `empty($backtestDetails)` 在两个方法中均存在(空预测保护)
|
||
- grep 匹配: `rank_1|rank_2|rank_3|rank_4|rank_5` 在 _calculateHitDistribution 中存在
|
||
- 两个方法均包含函数级注释
|
||
</acceptance_criteria>
|
||
|
||
### Task 3: 扩展 _runBacktestV3 返回结果(含数据量检查)
|
||
|
||
<read_first>
|
||
- D:\code\php\amlhc\application\admin\model\History.php (line 3549-3556, _runBacktestV3 返回语句)
|
||
</read_first>
|
||
|
||
<action>
|
||
修改 `_runBacktestV3` 方法的返回语句,在原有返回结构中添加新指标和数据量验证:
|
||
|
||
找到以下代码段(约 line 3549-3556):
|
||
```php
|
||
return [
|
||
'hit_rate' => $hitRate,
|
||
'avg_rank' => $avgRank,
|
||
'total_tests' => $testCount,
|
||
'total_hits' => $hits,
|
||
'details' => $details
|
||
];
|
||
```
|
||
|
||
替换为:
|
||
```php
|
||
// 计算新增指标(添加数据量检查)
|
||
$minDataThreshold = 50; // 置信度计算最小数据量阈值
|
||
|
||
// 如果测试数据不足,返回默认值并添加警告
|
||
if ($testCount < $minDataThreshold) {
|
||
$ndcg5 = 0;
|
||
$mrr = 0;
|
||
$hitDistribution = [
|
||
'rank_1' => 0,
|
||
'rank_2' => 0,
|
||
'rank_3' => 0,
|
||
'rank_4' => 0,
|
||
'rank_5' => 0
|
||
];
|
||
$dataWarning = '回测数据不足(' . $testCount . '期),建议至少50期以获得可靠指标';
|
||
} else {
|
||
$ndcg5 = $this->_calculateNDCG($details, 5);
|
||
$mrr = $this->_calculateMRR($details);
|
||
$hitDistribution = $this->_calculateHitDistribution($details);
|
||
$dataWarning = null;
|
||
}
|
||
|
||
$precision5 = $testCount > 0 ? round($hits / ($testCount * 5) * 100, 2) : 0;
|
||
|
||
return [
|
||
'hit_rate' => $hitRate,
|
||
'avg_rank' => $avgRank,
|
||
'total_tests' => $testCount,
|
||
'total_hits' => $hits,
|
||
'details' => $details,
|
||
// 新增排名质量指标
|
||
'ndcg_5' => $ndcg5,
|
||
'mrr' => $mrr,
|
||
'hit_distribution' => $hitDistribution,
|
||
'precision_5' => $precision5,
|
||
// 数据量警告(不足时提示)
|
||
'data_warning' => $dataWarning,
|
||
'data_sufficient' => $testCount >= $minDataThreshold
|
||
];
|
||
```
|
||
|
||
注意:
|
||
- 新增指标计算放在 return 语句之前,确保 $details 数组已完整构建
|
||
- 添加最小数据量检查(50期),不足时返回默认值和警告提示
|
||
- 新增 data_warning 和 data_sufficient 字段供前端展示
|
||
</action>
|
||
|
||
<acceptance_criteria>
|
||
- grep 匹配: `ndcg_5` 在 _runBacktestV3 返回结构中存在
|
||
- grep 匹配: `mrr` 在 _runBacktestV3 返回结构中存在
|
||
- grep 匹配: `hit_distribution` 在 _runBacktestV3 返回结构中存在
|
||
- grep 匹配: `precision_5` 在 _runBacktestV3 返回结构中存在
|
||
- grep 匹配: `data_warning` 在 _runBacktestV3 返回结构中存在
|
||
- grep 匹配: `minDataThreshold` 变量在方法中存在
|
||
</acceptance_criteria>
|
||
|
||
## Verification
|
||
|
||
执行预测接口验证新指标返回:
|
||
|
||
```bash
|
||
curl -s "http://127.0.0.1:8000/admin/history/predictV3?periods=200&backtest=10" | grep -E "ndcg_5|mrr|hit_distribution|precision_5|data_warning"
|
||
```
|
||
|
||
预期结果:返回 JSON 中包含 ndcg_5、mrr、hit_distribution、precision_5、data_warning 字段。
|
||
|
||
## Success Criteria
|
||
|
||
1. `_calculateNDCG`、`_calculateMRR`、`_calculateHitDistribution` 三个方法已实现
|
||
2. 所有计算方法包含空预测保护和数据完整性检查
|
||
3. NDCG 公式在注释中完整说明:DCG = Σ(1/log2(rank+1))
|
||
4. hit_distribution 结构明确为 `{rank_1..rank_5: counts}` 格式
|
||
5. `_runBacktestV3` 返回结构包含 ndcg_5、mrr、hit_distribution、precision_5、data_warning 字段
|
||
6. 添加数据量检查,不足50期时返回警告
|
||
7. 所有新增方法包含函数级注释
|
||
|
||
## Output
|
||
|
||
完成后创建 `.planning/phases/11-predictv3/11-01-SUMMARY.md` |