feat(11-05): 实现二阶马尔可夫转移概率增强

- 新增 _getTransitionMatrix2ndOrder 方法构建二阶转移矩阵
- 新增 _calcTransitionScore2ndOrder 方法计算二阶转移得分
- getPredictionV3 根据数据量和状态对观察次数自动选择阶数
- 阈值: 历史期数>=200,状态对观察>=5次,比例>=30%
- analysis 返回新增 transition_order、transition_available 字段
This commit is contained in:
2026-05-01 15:21:46 +08:00
parent 7853ce3e28
commit aab18dfd9e
+234 -3
View File
@@ -2227,12 +2227,49 @@ class History extends Model
} }
$expectedFreq10 = 10 / 49; $expectedFreq10 = 10 / 49;
// ====== 3. 转移概率分析(新增)====== // ====== 3. 转移概率分析 ======
// 获取转移概率矩阵数据 // 根据历史数据量决定使用一阶或二阶马尔可夫
// 阈值条件:总期数 >= 200 且 状态对观察次数充足(>=5次的比例>=30%
$minPeriodsThreshold = 200; // 二阶马尔可夫最小历史期数阈值(从100提升到200)
$minStatePairCount = 5; // 状态对最小观察次数
$use2ndOrder = false;
$secondOrderAvailable = false;
// 获取一阶转移概率矩阵(始终计算,作为fallback)
$zoneTransition = $this->_getTransitionMatrix($allHistory, 'zone'); $zoneTransition = $this->_getTransitionMatrix($allHistory, 'zone');
$tailTransition = $this->_getTransitionMatrix($allHistory, 'tail'); $tailTransition = $this->_getTransitionMatrix($allHistory, 'tail');
$headTransition = $this->_getTransitionMatrix($allHistory, 'head'); $headTransition = $this->_getTransitionMatrix($allHistory, 'head');
// 获取二阶转移概率矩阵(数据充足时)
$zoneTransition2nd = null;
$tailTransition2nd = null;
$headTransition2nd = null;
$prev2Zone = 0;
$prev2Tail = 0;
$prev2Head = 0;
if (count($allHistory) >= $minPeriodsThreshold && count($allHistory) >= 2) {
// 获取前两期号码属性
$prev2Special = (int)$allHistory[1]['num7'];
$prev2Zone = $this->_getZoneIdx($prev2Special);
$prev2Tail = $prev2Special % 10;
$prev2Head = $this->_getHeadIdx($prev2Special);
// 构建二阶转移矩阵
$zoneTransition2nd = $this->_getTransitionMatrix2ndOrder($allHistory, 'zone', $minStatePairCount);
$tailTransition2nd = $this->_getTransitionMatrix2ndOrder($allHistory, 'tail', $minStatePairCount);
$headTransition2nd = $this->_getTransitionMatrix2ndOrder($allHistory, 'head', $minStatePairCount);
// 检查状态对观察次数是否充足(至少30%的状态对有足够观察)
// tail类型状态空间最大(100),以tail为基准判断
if ($tailTransition2nd['total_pairs'] > 0) {
$sufficientRatio = $tailTransition2nd['sufficient_pairs'] / $tailTransition2nd['total_pairs'];
$secondOrderAvailable = $sufficientRatio >= 0.3; // 至少30%状态对观察>=5次
}
$use2ndOrder = $secondOrderAvailable;
}
// 上期号码的各类属性 // 上期号码的各类属性
$lastZone = $this->_getZoneIdx($lastSpecial); $lastZone = $this->_getZoneIdx($lastSpecial);
$lastTail = $lastSpecial % 10; $lastTail = $lastSpecial % 10;
@@ -2304,6 +2341,10 @@ class History extends Model
'oddeven_stats' => $oddevenStats, 'oddeven_stats' => $oddevenStats,
'bigsmall_stats' => $bigsmallStats, 'bigsmall_stats' => $bigsmallStats,
'trend_direction' => $trendDirection, 'trend_direction' => $trendDirection,
'transition_order' => $use2ndOrder ? 2 : 1, // 转移概率阶数
'transition_available' => $secondOrderAvailable, // 二阶是否可用
'history_count' => count($allHistory), // 历史期数
'min_periods_threshold' => $minPeriodsThreshold, // 二阶阈值
'last_zone' => $zoneLabels[$lastZone] ?? '', 'last_zone' => $zoneLabels[$lastZone] ?? '',
'last_tail' => $lastTail, 'last_tail' => $lastTail,
'last_head' => $lastHead, 'last_head' => $lastHead,
@@ -2339,12 +2380,22 @@ class History extends Model
$detail['freq_score'] = round($freqScore, 2); $detail['freq_score'] = round($freqScore, 2);
$totalScore += $freqScore * $weights['freq_regression']; $totalScore += $freqScore * $weights['freq_regression'];
// === 转移概率得分(新增=== // === 转移概率得分(根据阶数选择计算方法===
if ($use2ndOrder && $zoneTransition2nd && $tailTransition2nd && $headTransition2nd) {
$transScore = $this->_calcTransitionScore2ndOrder(
$num, $lastZone, $prev2Zone, $lastTail, $prev2Tail, $lastHead, $prev2Head,
$zoneTransition2nd, $tailTransition2nd, $headTransition2nd,
$zoneMap, $tailMap, $headMap
);
$detail['trans_order'] = 2;
} else {
$transScore = $this->_calcTransitionScore( $transScore = $this->_calcTransitionScore(
$num, $lastZone, $lastTail, $lastHead, $num, $lastZone, $lastTail, $lastHead,
$zoneTransition, $tailTransition, $headTransition, $zoneTransition, $tailTransition, $headTransition,
$zoneMap, $tailMap, $headMap $zoneMap, $tailMap, $headMap
); );
$detail['trans_order'] = 1;
}
$detail['trans_score'] = round($transScore, 2); $detail['trans_score'] = round($transScore, 2);
$totalScore += $transScore * $weights['transition_prob']; $totalScore += $transScore * $weights['transition_prob'];
@@ -2541,6 +2592,124 @@ class History extends Model
]; ];
} }
/**
* 构建二阶马尔可夫转移矩阵
* 考虑前两期状态联合决定当前转移概率
*
* 状态空间说明:
* - 一阶马尔可夫: N个状态 (zone:5, tail:10, head:5)
* - 二阶马尔可夫: N^2个状态对 (zone:25, tail:100, head:25)
* - 状态键格式: "prev1-prev2",如 "2-3" 表示前一期区域2、前两期区域3
*
* 数据量阈值说明:
* - 建议历史数据 >= 200期以获得稳定的二阶概率估计
* - 状态对观察次数 >= 5 才使用该状态对的二阶概率
* - 观察次数不足时返回 state_pair_insufficient 标志,供调用者回退一阶
*
* @param array $history 历史数据(降序,最新在前)
* @param string $type 类型:zone/tail/head
* @param int $minStatePairCount 状态对最小观察次数,默认5
* @return array {matrix: [], prob_matrix: [], state_totals: [], num_categories: int, sufficient_pairs: int, total_pairs: int, min_threshold: int}
*/
private function _getTransitionMatrix2ndOrder($history, $type, $minStatePairCount = 5)
{
// 升序排列(从旧到新)
$historyAsc = array_reverse($history);
// 确定类别数量和索引函数
switch ($type) {
case 'zone':
$numCategories = 5;
$getIdx = function ($num) {
if ($num <= 10) return 0;
if ($num <= 20) return 1;
if ($num <= 30) return 2;
if ($num <= 40) return 3;
return 4;
};
break;
case 'tail':
$numCategories = 10;
$getIdx = function ($num) { return $num % 10; };
break;
case 'head':
$numCategories = 5;
$getIdx = function ($num) {
if ($num <= 9) return 0;
if ($num <= 19) return 1;
if ($num <= 29) return 2;
if ($num <= 39) return 3;
return 4;
};
break;
default:
return [
'matrix' => [],
'prob_matrix' => [],
'state_totals' => [],
'num_categories' => 0,
'sufficient_pairs' => 0,
'total_pairs' => 0,
'min_threshold' => $minStatePairCount
];
}
// 状态空间: (prev1, prev2) -> current,共 numCategories^2 个前置状态
$matrix = [];
$stateTotals = [];
// 初始化矩阵结构
for ($i = 0; $i < $numCategories; $i++) {
for ($j = 0; $j < $numCategories; $j++) {
$stateKey = $i . '-' . $j;
$matrix[$stateKey] = array_fill(0, $numCategories, 0);
$stateTotals[$stateKey] = 0;
}
}
// 统计二阶转移
for ($i = 0; $i < count($historyAsc) - 2; $i++) {
$prev1 = $getIdx((int)$historyAsc[$i]['num7']);
$prev2 = $getIdx((int)$historyAsc[$i + 1]['num7']);
$current = $getIdx((int)$historyAsc[$i + 2]['num7']);
if ($prev1 < 0 || $prev2 < 0 || $current < 0) continue;
$stateKey = $prev1 . '-' . $prev2;
$matrix[$stateKey][$current]++;
$stateTotals[$stateKey]++;
}
// 统计充分观察的状态对数量(观察次数 >= minStatePairCount
$sufficientPairs = 0;
$totalPairs = $numCategories * $numCategories;
foreach ($stateTotals as $stateKey => $count) {
if ($count >= $minStatePairCount) {
$sufficientPairs++;
}
}
// 拉普拉斯平滑处理
$probMatrix = [];
foreach ($matrix as $stateKey => $counts) {
$smoothTotal = $stateTotals[$stateKey] + $numCategories;
$probMatrix[$stateKey] = [];
for ($j = 0; $j < $numCategories; $j++) {
$probMatrix[$stateKey][$j] = ($counts[$j] + 1) / $smoothTotal;
}
}
return [
'matrix' => $matrix,
'prob_matrix' => $probMatrix,
'state_totals' => $stateTotals,
'num_categories' => $numCategories,
'sufficient_pairs' => $sufficientPairs,
'total_pairs' => $totalPairs,
'min_threshold' => $minStatePairCount
];
}
/** /**
* 分析单双规律 * 分析单双规律
* @param array $history 历史数据(降序) * @param array $history 历史数据(降序)
@@ -3090,6 +3259,68 @@ class History extends Model
return min(100, $score); return min(100, $score);
} }
/**
* 计算二阶转移概率得分
*
* 计算方法:
* - 综合区域、尾号、首号三个维度的二阶转移概率
* - 各维度权重: 区域40%、尾号35%、首号25%
* - 得分范围: 0-100
*
* @param int $num 当前号码
* @param int $prev1Zone 前一期区域索引
* @param int $prev2Zone 前两期区域索引
* @param int $prev1Tail 前一期尾号索引
* @param int $prev2Tail 前两期尾号索引
* @param int $prev1Head 前一期首号索引
* @param int $prev2Head 前两期首号索引
* @param array $zoneTrans2nd 二阶区域转移矩阵
* @param array $tailTrans2nd 二阶尾号转移矩阵
* @param array $headTrans2nd 二阶首号转移矩阵
* @param array $zoneMap 号码区域映射
* @param array $tailMap 号码尾号映射
* @param array $headMap 号码首号映射
* @return float 综合转移得分 (0-100)
*/
private function _calcTransitionScore2ndOrder(
$num,
$prev1Zone, $prev2Zone,
$prev1Tail, $prev2Tail,
$prev1Head, $prev2Head,
$zoneTrans2nd, $tailTrans2nd, $headTrans2nd,
$zoneMap, $tailMap, $headMap
)
{
$zone = $zoneMap[$num];
$tail = $tailMap[$num];
$head = $headMap[$num];
$score = 0;
// 区域二阶转移得分(权重40%
$zoneStateKey = $prev1Zone . '-' . $prev2Zone;
if (isset($zoneTrans2nd['prob_matrix'][$zoneStateKey][$zone])) {
$prob = $zoneTrans2nd['prob_matrix'][$zoneStateKey][$zone];
$score += $prob * 40;
}
// 尾号二阶转移得分(权重35%
$tailStateKey = $prev1Tail . '-' . $prev2Tail;
if (isset($tailTrans2nd['prob_matrix'][$tailStateKey][$tail])) {
$prob = $tailTrans2nd['prob_matrix'][$tailStateKey][$tail];
$score += $prob * 35;
}
// 首号二阶转移得分(权重25%
$headStateKey = $prev1Head . '-' . $prev2Head;
if (isset($headTrans2nd['prob_matrix'][$headStateKey][$head])) {
$prob = $headTrans2nd['prob_matrix'][$headStateKey][$head];
$score += $prob * 25;
}
return round($score, 2);
}
/** /**
* 计算单双平衡得分 * 计算单双平衡得分
* @param int $num 待评分号码 * @param int $num 待评分号码