From aab18dfd9edf9b9966396612b9d210609b2284fe Mon Sep 17 00:00:00 2001 From: leon <916117771@qq.com> Date: Fri, 1 May 2026 15:21:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(11-05):=20=E5=AE=9E=E7=8E=B0=E4=BA=8C?= =?UTF-8?q?=E9=98=B6=E9=A9=AC=E5=B0=94=E5=8F=AF=E5=A4=AB=E8=BD=AC=E7=A7=BB?= =?UTF-8?q?=E6=A6=82=E7=8E=87=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 _getTransitionMatrix2ndOrder 方法构建二阶转移矩阵 - 新增 _calcTransitionScore2ndOrder 方法计算二阶转移得分 - getPredictionV3 根据数据量和状态对观察次数自动选择阶数 - 阈值: 历史期数>=200,状态对观察>=5次,比例>=30% - analysis 返回新增 transition_order、transition_available 字段 --- application/admin/model/History.php | 247 +++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 8 deletions(-) diff --git a/application/admin/model/History.php b/application/admin/model/History.php index a964487..5ebc11d 100644 --- a/application/admin/model/History.php +++ b/application/admin/model/History.php @@ -2227,12 +2227,49 @@ class History extends Model } $expectedFreq10 = 10 / 49; - // ====== 3. 转移概率分析(新增)====== - // 获取转移概率矩阵数据 + // ====== 3. 转移概率分析 ====== + // 根据历史数据量决定使用一阶或二阶马尔可夫 + // 阈值条件:总期数 >= 200 且 状态对观察次数充足(>=5次的比例>=30%) + $minPeriodsThreshold = 200; // 二阶马尔可夫最小历史期数阈值(从100提升到200) + $minStatePairCount = 5; // 状态对最小观察次数 + $use2ndOrder = false; + $secondOrderAvailable = false; + + // 获取一阶转移概率矩阵(始终计算,作为fallback) $zoneTransition = $this->_getTransitionMatrix($allHistory, 'zone'); $tailTransition = $this->_getTransitionMatrix($allHistory, 'tail'); $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); $lastTail = $lastSpecial % 10; @@ -2304,6 +2341,10 @@ class History extends Model 'oddeven_stats' => $oddevenStats, 'bigsmall_stats' => $bigsmallStats, 'trend_direction' => $trendDirection, + 'transition_order' => $use2ndOrder ? 2 : 1, // 转移概率阶数 + 'transition_available' => $secondOrderAvailable, // 二阶是否可用 + 'history_count' => count($allHistory), // 历史期数 + 'min_periods_threshold' => $minPeriodsThreshold, // 二阶阈值 'last_zone' => $zoneLabels[$lastZone] ?? '', 'last_tail' => $lastTail, 'last_head' => $lastHead, @@ -2339,12 +2380,22 @@ class History extends Model $detail['freq_score'] = round($freqScore, 2); $totalScore += $freqScore * $weights['freq_regression']; - // === 转移概率得分(新增)=== - $transScore = $this->_calcTransitionScore( - $num, $lastZone, $lastTail, $lastHead, - $zoneTransition, $tailTransition, $headTransition, - $zoneMap, $tailMap, $headMap - ); + // === 转移概率得分(根据阶数选择计算方法)=== + 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( + $num, $lastZone, $lastTail, $lastHead, + $zoneTransition, $tailTransition, $headTransition, + $zoneMap, $tailMap, $headMap + ); + $detail['trans_order'] = 1; + } $detail['trans_score'] = round($transScore, 2); $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 历史数据(降序) @@ -3090,6 +3259,68 @@ class History extends Model 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 待评分号码