feat: 彩票技能统一输出格式 - 双色球/大乐透4块结构化输出标准对齐
- 新增 model_full_report_dlt.py: 大乐透专用全量报告脚本 - 统一4块结构: 回滚迭代→数据采集→推理分析→机选号码 - 统一术语: 前区/后区(大乐透) 对齐 红球/蓝球(双色球) - 统一评分: TOP10前区+TOP5后区 归一化概率输出 - 更新 SKILL.md: 双色球/大乐透统一模板+文件结构
This commit is contained in:
1 parent
f45881dc88
commit
c4bb8632ca
15 files changed
+9932
No files matched your search
@@ -0,0 +1,725 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
大乐透/双色球 集成学习模块
|
||||
方案3:多模型集成 + 权重优化训练
|
||||
==================================
|
||||
包含5种分析模型:
|
||||
1. 频率加权模型 (Frequency Model)
|
||||
2. 贝叶斯平滑模型 (Bayesian Smoothing Model)
|
||||
3. 马尔可夫链模型 (Markov Chain Model)
|
||||
4. K-Means聚类模型 (K-Means Clustering Model)
|
||||
5. 残差回归模型 (Residual Regression Model)
|
||||
|
||||
通过历史数据回测学习各模型的最优权重
|
||||
"""
|
||||
|
||||
import json, math, random, hashlib
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
# ==================== 基础工具函数 ====================
|
||||
|
||||
def sigmoid(x):
|
||||
"""Sigmoid激活函数"""
|
||||
return 1 / (1 + math.exp(-max(min(x, 500), -500)))
|
||||
|
||||
def normalize(scores):
|
||||
"""归一化分数,使总和为1"""
|
||||
total = sum(scores.values())
|
||||
if total == 0:
|
||||
return {k: 1.0/len(scores) for k in scores}
|
||||
return {k: v/total for k, v in scores.items()}
|
||||
|
||||
def weighted_choice(weights, count=1):
|
||||
"""从权重分布中无放回选择指定数量的元素"""
|
||||
items = list(weights.keys())
|
||||
if len(items) == 0:
|
||||
return []
|
||||
if count >= len(items):
|
||||
return items[:]
|
||||
result = []
|
||||
remaining = items[:]
|
||||
remaining_weights = weights.copy()
|
||||
for _ in range(count):
|
||||
total = sum(remaining_weights.values())
|
||||
if total <= 0:
|
||||
choice = random.choice(remaining)
|
||||
else:
|
||||
r = random.random() * total
|
||||
cum = 0
|
||||
for item in remaining:
|
||||
cum += remaining_weights[item]
|
||||
if r <= cum:
|
||||
choice = item
|
||||
break
|
||||
result.append(choice)
|
||||
remaining.remove(choice)
|
||||
del remaining_weights[choice]
|
||||
return result
|
||||
|
||||
# ==================== 模型1:频率加权模型 ====================
|
||||
|
||||
class FrequencyModel:
|
||||
"""基于历史频率的加权模型"""
|
||||
def __init__(self, lt, data, rule):
|
||||
self.lt = lt
|
||||
self.data = data
|
||||
self.rule = rule
|
||||
self.rcnt = rule["red_count"]
|
||||
self.rmax = rule["red_max"]
|
||||
self.bcnt = rule["blue_count"]
|
||||
self.bmax = rule["blue_max"]
|
||||
self.red_score = {}
|
||||
self.blue_score = {}
|
||||
self.analyze()
|
||||
|
||||
def analyze(self):
|
||||
rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"]
|
||||
all_r, all_b = [], []
|
||||
for rec in self.data:
|
||||
all_r.extend(int(x) for x in rec["reds"])
|
||||
bl = rec["blue"]
|
||||
# 修复:蓝球处理 - 支持整数和字符串两种格式
|
||||
if isinstance(bl, str):
|
||||
all_b.append(int(bl))
|
||||
elif isinstance(bl, list):
|
||||
all_b.extend(bl)
|
||||
else:
|
||||
all_b.append(bl)
|
||||
|
||||
# 频率统计
|
||||
rf = Counter(all_r)
|
||||
bf = Counter(all_b)
|
||||
|
||||
max_rf = max(rf.values()) if rf else 1
|
||||
max_bf = max(bf.values()) if bf else 1
|
||||
|
||||
# 归一化
|
||||
self.red_score = {i: rf.get(i, 0) / max_rf for i in range(1, rmax + 1)}
|
||||
self.blue_score = {i: bf.get(i, 0) / max_bf for i in range(1, bmax + 1)}
|
||||
|
||||
# 平滑:避免0分
|
||||
for i in range(1, rmax + 1):
|
||||
if self.red_score[i] == 0:
|
||||
self.red_score[i] = 0.01
|
||||
for i in range(1, bmax + 1):
|
||||
if self.blue_score[i] == 0:
|
||||
self.blue_score[i] = 0.01
|
||||
|
||||
# 归一化到[0,1]
|
||||
max_r = max(self.red_score.values())
|
||||
max_b = max(self.blue_score.values())
|
||||
if max_r > 0:
|
||||
for i in self.red_score:
|
||||
self.red_score[i] /= max_r
|
||||
if max_b > 0:
|
||||
for i in self.blue_score:
|
||||
self.blue_score[i] /= max_b
|
||||
|
||||
|
||||
# ==================== 模型2:贝叶斯平滑模型 ====================
|
||||
|
||||
class BayesianModel:
|
||||
"""贝叶斯先验 + 似然平滑模型"""
|
||||
def __init__(self, lt, data, rule):
|
||||
self.lt = lt
|
||||
self.data = data
|
||||
self.rule = rule
|
||||
self.rcnt = rule["red_count"]
|
||||
self.rmax = rule["red_max"]
|
||||
self.bcnt = rule["blue_count"]
|
||||
self.bmax = rule["blue_max"]
|
||||
self.prior = {i: 1.0 / rule["red_max"] for i in range(1, rule["red_max"] + 1)}
|
||||
self.red_score = {}
|
||||
self.blue_score = {}
|
||||
self.analyze()
|
||||
|
||||
def analyze(self):
|
||||
rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"]
|
||||
all_r, all_b = [], []
|
||||
for rec in self.data:
|
||||
all_r.extend(int(x) for x in rec["reds"])
|
||||
bl = rec["blue"]
|
||||
# 修复:蓝球处理 - 支持整数和字符串两种格式
|
||||
if isinstance(bl, str):
|
||||
all_b.append(int(bl))
|
||||
elif isinstance(bl, list):
|
||||
all_b.extend(bl)
|
||||
else:
|
||||
all_b.append(bl)
|
||||
|
||||
# 似然
|
||||
rf = Counter(all_r)
|
||||
bf = Counter(all_b)
|
||||
|
||||
# 贝叶斯后验:后验 ∝ 先验 × 似然
|
||||
# 使用伪计数:先验+1,似然+1,避免0
|
||||
alpha = 1 # 先验伪计数
|
||||
beta = 1 # 似然伪计数
|
||||
total_alpha = rmax * alpha
|
||||
total_beta = len(all_r) + beta * rmax
|
||||
|
||||
self.red_score = {}
|
||||
for i in range(1, rmax + 1):
|
||||
# 后验均值 = (α + count) / (α + β * rmax)
|
||||
posterior = (alpha + rf.get(i, 0)) / (total_alpha + beta)
|
||||
self.red_score[i] = posterior
|
||||
|
||||
total_blue = len(all_b) + beta * bmax
|
||||
self.blue_score = {}
|
||||
for i in range(1, bmax + 1):
|
||||
posterior = (alpha + bf.get(i, 0)) / (total_alpha + beta)
|
||||
self.blue_score[i] = posterior
|
||||
|
||||
# 归一化
|
||||
max_r = max(self.red_score.values())
|
||||
max_b = max(self.blue_score.values())
|
||||
if max_r > 0:
|
||||
for i in self.red_score:
|
||||
self.red_score[i] /= max_r
|
||||
if max_b > 0:
|
||||
for i in self.blue_score:
|
||||
self.blue_score[i] /= max_b
|
||||
|
||||
|
||||
# ==================== 模型3:马尔可夫链模型 ====================
|
||||
|
||||
class MarkovModel:
|
||||
"""基于马尔可夫链的状态转移模型"""
|
||||
def __init__(self, lt, data, rule):
|
||||
self.lt = lt
|
||||
self.data = data
|
||||
self.rule = rule
|
||||
self.rcnt = rule["red_count"]
|
||||
self.rmax = rule["red_max"]
|
||||
self.bcnt = rule["blue_count"]
|
||||
self.bmax = rule["blue_max"]
|
||||
self.red_score = {}
|
||||
self.blue_score = {}
|
||||
self.analyze()
|
||||
|
||||
def analyze(self):
|
||||
rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"]
|
||||
|
||||
# 构建状态转移矩阵(考虑最近5期作为状态)
|
||||
# 这里简化为:计算每个号码在历史数据中的"惯性"
|
||||
# 即:一个号码出现后,后续几期再次出现的概率
|
||||
|
||||
# 使用frozenset作为不可变集合作为字典键
|
||||
transition = defaultdict(lambda: defaultdict(int))
|
||||
for i in range(1, len(self.data)):
|
||||
prev = frozenset(int(x) for x in self.data[i-1]["reds"])
|
||||
curr = set(int(x) for x in self.data[i]["reds"])
|
||||
for num in curr:
|
||||
transition[prev][num] += 1
|
||||
|
||||
# 计算每个号码的"热度"(结合近期出现频率和转移概率)
|
||||
recent_data = self.data[:20] if len(self.data) >= 20 else self.data
|
||||
recent_reds = []
|
||||
for rec in recent_data:
|
||||
recent_reds.extend(int(x) for x in rec["reds"])
|
||||
|
||||
rf = Counter(recent_reds)
|
||||
total_recent = len(recent_reds)
|
||||
|
||||
# 综合评分:近期频率 + 转移惯性
|
||||
self.red_score = {}
|
||||
for i in range(1, rmax + 1):
|
||||
freq_score = rf.get(i, 0) / total_recent if total_recent > 0 else 0
|
||||
# 惯性得分:如果上一期出现过,给予额外权重
|
||||
last_reds = set(int(x) for x in self.data[0]["reds"]) if self.data else set()
|
||||
inertia = 1.5 if i in last_reds else 1.0
|
||||
self.red_score[i] = (freq_score * inertia + 0.01) # 加0.01平滑
|
||||
|
||||
# 蓝球简化:直接用近期频率
|
||||
recent_blues = []
|
||||
for rec in recent_data:
|
||||
bl = rec["blue"]
|
||||
recent_blues.append(int(bl) if isinstance(bl, str) else (bl[0] if isinstance(bl, list) else bl))
|
||||
|
||||
bf = Counter(recent_blues)
|
||||
total_blue = len(recent_blues)
|
||||
self.blue_score = {}
|
||||
for i in range(1, bmax + 1):
|
||||
self.blue_score[i] = (bf.get(i, 0) / total_blue * 0.8 + 1.0 / bmax * 0.2) if total_blue > 0 else (1.0 / bmax)
|
||||
|
||||
# 归一化
|
||||
max_r = max(self.red_score.values()) if self.red_score else 1
|
||||
max_b = max(self.blue_score.values()) if self.blue_score else 1
|
||||
for i in self.red_score:
|
||||
self.red_score[i] /= max_r
|
||||
for i in self.blue_score:
|
||||
self.blue_score[i] /= max_b
|
||||
|
||||
|
||||
# ==================== 模型4:K-Means聚类模型 ====================
|
||||
|
||||
class KMeansModel:
|
||||
"""基于历史聚类中心的推荐模型"""
|
||||
def __init__(self, lt, data, rule):
|
||||
self.lt = lt
|
||||
self.data = data
|
||||
self.rule = rule
|
||||
self.rcnt = rule["red_count"]
|
||||
self.rmax = rule["red_max"]
|
||||
self.bcnt = rule["blue_count"]
|
||||
self.bmax = rule["blue_max"]
|
||||
self.red_score = {}
|
||||
self.blue_score = {}
|
||||
self.analyze()
|
||||
|
||||
def analyze(self):
|
||||
rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_max"]
|
||||
|
||||
# 提取所有历史红球组合
|
||||
clusters = []
|
||||
for rec in self.data:
|
||||
nums = sorted(int(x) for x in rec["reds"])
|
||||
if len(nums) == rcnt:
|
||||
clusters.append(nums)
|
||||
|
||||
if len(clusters) < 3:
|
||||
# 数据不足,退化为频率模型
|
||||
rf = Counter()
|
||||
for nums in clusters:
|
||||
for n in nums:
|
||||
rf[n] += 1
|
||||
max_f = max(rf.values()) if rf else 1
|
||||
self.red_score = {i: rf.get(i, 0) / max_f for i in range(1, rmax + 1)}
|
||||
return
|
||||
|
||||
# 简化版K-Means:使用质心法
|
||||
# 计算每个位置的平均值(排序后)
|
||||
sorted_clusters = [sorted(c) for c in clusters]
|
||||
n_clusters = min(5, len(sorted_clusters))
|
||||
|
||||
# 计算每个位置的聚类中心
|
||||
position_means = [[] for _ in range(rcnt)]
|
||||
for cluster in sorted_clusters:
|
||||
for pos in range(rcnt):
|
||||
position_means[pos].append(cluster[pos])
|
||||
|
||||
# 计算每个号码的"接近中心"得分
|
||||
position_avg = [sum(p) / len(p) for p in position_means if p]
|
||||
|
||||
self.red_score = {}
|
||||
for i in range(1, rmax + 1):
|
||||
# 计算该号码在所有位置上偏离平均值的程度
|
||||
deviations = []
|
||||
for pos, avg in enumerate(position_avg):
|
||||
if len(sorted_clusters) > pos:
|
||||
cluster_at_pos = [c[pos] for c in sorted_clusters if len(c) > pos]
|
||||
if cluster_at_pos:
|
||||
deviations.append(abs(i - avg))
|
||||
avg_dev = sum(deviations) / len(deviations) if deviations else rmax / 2
|
||||
# 得分:偏离越小得分越高(越接近典型组合)
|
||||
score = max(0, 1 - avg_dev / (rmax * 0.5))
|
||||
self.red_score[i] = max(score, 0.01) # 最小0.01
|
||||
|
||||
# 蓝球:直接用频率
|
||||
all_blues = []
|
||||
for rec in self.data:
|
||||
bl = rec["blue"]
|
||||
all_blues.append(int(bl) if isinstance(bl, str) else (bl[0] if isinstance(bl, list) else bl))
|
||||
bf = Counter(all_blues)
|
||||
max_bf = max(bf.values()) if bf else 1
|
||||
self.blue_score = {i: bf.get(i, 0) / max_bf for i in range(1, bmax + 1)}
|
||||
for i in range(1, bmax + 1):
|
||||
if self.blue_score[i] == 0:
|
||||
self.blue_score[i] = 0.01
|
||||
|
||||
# 归一化
|
||||
max_r = max(self.red_score.values()) if self.red_score else 1
|
||||
max_b = max(self.blue_score.values()) if self.blue_score else 1
|
||||
for i in self.red_score:
|
||||
self.red_score[i] /= max_r
|
||||
for i in self.blue_score:
|
||||
self.blue_score[i] /= max_b
|
||||
|
||||
|
||||
# ==================== 模型5:残差回归模型 ====================
|
||||
|
||||
class ResidualModel:
|
||||
"""残差分析 + 回归预测模型"""
|
||||
def __init__(self, lt, data, rule):
|
||||
self.lt = lt
|
||||
self.data = data
|
||||
self.rule = rule
|
||||
self.rcnt = rule["red_count"]
|
||||
self.rmax = rule["red_max"]
|
||||
self.bcnt = rule["blue_count"]
|
||||
self.bmax = rule["blue_max"]
|
||||
self.red_score = {}
|
||||
self.blue_score = {}
|
||||
self.analyze()
|
||||
|
||||
def analyze(self):
|
||||
rule, rmax, bmax, rcnt, bcnt = self.rule, self.rule["red_max"], self.rule["blue_max"], self.rule["red_count"], self.rule["blue_count"]
|
||||
|
||||
# 计算每期的和值、奇偶比、大小比作为特征
|
||||
sums = []
|
||||
for rec in self.data:
|
||||
s = sum(int(x) for x in rec["reds"])
|
||||
sums.append(s)
|
||||
|
||||
if len(sums) < 10:
|
||||
# 数据不足,退化为均匀分布
|
||||
self.red_score = {i: 1.0 / rmax for i in range(1, rmax + 1)}
|
||||
self.blue_score = {i: 1.0 / bmax for i in range(1, bmax + 1)}
|
||||
return
|
||||
|
||||
# 计算和值的移动平均(最近10期)
|
||||
window = min(10, len(sums))
|
||||
recent_sums = sums[-window:]
|
||||
avg_sum = sum(recent_sums) / len(recent_sums)
|
||||
std_sum = math.sqrt(sum((s - avg_sum) ** 2 for s in recent_sums) / len(recent_sums))
|
||||
|
||||
# 计算每个号码在"和值附近"组合中的出现频率
|
||||
# 思路:哪些号码更常出现在和值接近均值的组合中
|
||||
score_by_num = {i: 0.0 for i in range(1, rmax + 1)}
|
||||
count_by_num = {i: 0 for i in range(1, rmax + 1)}
|
||||
|
||||
for rec in self.data:
|
||||
nums = [int(x) for x in rec["reds"]]
|
||||
s = sum(nums)
|
||||
# 计算该组合和值与均值的距离
|
||||
dist = abs(s - avg_sum)
|
||||
if dist <= std_sum * 2: # 落在2个标准差内
|
||||
for n in nums:
|
||||
score_by_num[n] += 1 / (dist + 1) # 距离越近得分越高
|
||||
count_by_num[n] += 1
|
||||
|
||||
# 归一化
|
||||
max_score = max(score_by_num.values()) if max(score_by_num.values()) > 0 else 1
|
||||
self.red_score = {}
|
||||
for i in range(1, rmax + 1):
|
||||
if count_by_num[i] > 0:
|
||||
self.red_score[i] = (score_by_num[i] / count_by_num[i]) / max_score
|
||||
else:
|
||||
self.red_score[i] = 1.0 / rmax # 均匀分布
|
||||
|
||||
# 蓝球:和值相关性分析
|
||||
blues_by_sum = defaultdict(list)
|
||||
for rec in self.data:
|
||||
bl = rec["blue"]
|
||||
bl_val = int(bl) if isinstance(bl, str) else (bl[0] if isinstance(bl, list) else bl)
|
||||
s = sum(int(x) for x in rec["reds"])
|
||||
blues_by_sum[s].append(bl_val)
|
||||
|
||||
self.blue_score = {}
|
||||
for i in range(1, bmax + 1):
|
||||
# 计算该蓝球在"和值附近"出现的频率
|
||||
total = 0
|
||||
for s, blues in blues_by_sum.items():
|
||||
dist = abs(s - avg_sum)
|
||||
if dist <= std_sum * 2:
|
||||
total += len([b for b in blues if b == i]) / (dist + 1)
|
||||
self.blue_score[i] = max(total / 100, 1.0 / bmax)
|
||||
|
||||
# 归一化
|
||||
max_r = max(self.red_score.values()) if self.red_score else 1
|
||||
max_b = max(self.blue_score.values()) if self.blue_score else 1
|
||||
for i in self.red_score:
|
||||
self.red_score[i] /= max_r
|
||||
for i in self.blue_score:
|
||||
self.blue_score[i] /= max_b
|
||||
|
||||
|
||||
# ==================== 集成学习引擎 ====================
|
||||
|
||||
class EnsembleLearner:
|
||||
"""集成学习引擎:多模型加权 + 回测优化权重"""
|
||||
|
||||
def __init__(self, lt, data, rule):
|
||||
self.lt = lt
|
||||
self.data = data
|
||||
self.rule = rule
|
||||
self.rcnt = rule["red_count"]
|
||||
self.rmax = rule["red_max"]
|
||||
self.bcnt = rule["blue_count"]
|
||||
self.bmax = rule["blue_max"]
|
||||
|
||||
# 初始化5个模型
|
||||
self.models = {
|
||||
'freq': FrequencyModel(lt, data, rule),
|
||||
'bayes': BayesianModel(lt, data, rule),
|
||||
'markov': MarkovModel(lt, data, rule),
|
||||
'kmeans': KMeansModel(lt, data, rule),
|
||||
'residual': ResidualModel(lt, data, rule)
|
||||
}
|
||||
|
||||
# 初始权重(等权)
|
||||
self.weights = {m: 1.0 / len(self.models) for m in self.models}
|
||||
self.initial_weights = self.weights.copy()
|
||||
|
||||
# 回测结果
|
||||
self.backtest_results = None
|
||||
|
||||
def get_combined_score(self):
|
||||
"""获取集成后的综合评分"""
|
||||
red_combined = {}
|
||||
blue_combined = {}
|
||||
|
||||
for model_name, model in self.models.items():
|
||||
w = self.weights[model_name]
|
||||
for i in range(1, self.rmax + 1):
|
||||
red_combined[i] = red_combined.get(i, 0) + w * model.red_score[i]
|
||||
for i in range(1, self.bmax + 1):
|
||||
blue_combined[i] = blue_combined.get(i, 0) + w * model.blue_score[i]
|
||||
|
||||
# 归一化
|
||||
max_r = max(red_combined.values()) if red_combined else 1
|
||||
max_b = max(blue_combined.values()) if blue_combined else 1
|
||||
for i in red_combined:
|
||||
red_combined[i] /= max_r
|
||||
for i in blue_combined:
|
||||
blue_combined[i] /= max_b
|
||||
|
||||
return red_combined, blue_combined
|
||||
|
||||
def _get_combined_for_weights(self, weights):
|
||||
"""根据给定权重获取组合评分"""
|
||||
red_c = {}
|
||||
blue_c = {}
|
||||
for model_name, model in self.models.items():
|
||||
ww = weights[model_name]
|
||||
for i in range(1, self.rmax + 1):
|
||||
red_c[i] = red_c.get(i, 0) + ww * model.red_score[i]
|
||||
for i in range(1, self.bmax + 1):
|
||||
blue_c[i] = blue_c.get(i, 0) + ww * model.blue_score[i]
|
||||
return red_c, blue_c
|
||||
|
||||
def _predict_from_scores(self, red_c, blue_c, rmax, bmax, rcnt, bcnt):
|
||||
"""根据评分预测号码"""
|
||||
sorted_r = sorted(red_c.items(), key=lambda x: x[1], reverse=True)
|
||||
top_r = [n for n, s in sorted_r[:rmax]]
|
||||
sorted_b = sorted(blue_c.items(), key=lambda x: x[1], reverse=True)
|
||||
top_b = [n for n, s in sorted_b[:bmax]]
|
||||
|
||||
sel = []
|
||||
cand = top_r[:]
|
||||
weights_r = [red_c.get(n, 0.5) for n in cand]
|
||||
while len(sel) < rcnt and cand:
|
||||
tw = sum(weights_r)
|
||||
if tw <= 0: pk = random.choice(cand)
|
||||
else:
|
||||
rv = random.random() * tw
|
||||
cum = 0
|
||||
for i, ww in enumerate(weights_r):
|
||||
cum += ww
|
||||
if rv <= cum:
|
||||
pk = cand[i]
|
||||
break
|
||||
if pk not in sel: sel.append(pk)
|
||||
idx = cand.index(pk); cand.pop(idx); weights_r.pop(idx)
|
||||
sel.sort()
|
||||
|
||||
bls = []
|
||||
cand_b = top_b[:]
|
||||
weights_b = [blue_c.get(n, 0.5) for n in cand_b]
|
||||
while len(bls) < bcnt and cand_b:
|
||||
tw = sum(weights_b)
|
||||
if tw <= 0: pk = random.choice(cand_b)
|
||||
else:
|
||||
rv = random.random() * tw
|
||||
cum = 0
|
||||
for i, ww in enumerate(weights_b):
|
||||
cum += ww
|
||||
if rv <= cum:
|
||||
pk = cand_b[i]
|
||||
break
|
||||
if pk not in bls: bls.append(pk)
|
||||
idx = cand_b.index(pk); cand_b.pop(idx); weights_b.pop(idx)
|
||||
bls.sort()
|
||||
|
||||
return sel, bls
|
||||
|
||||
def backtest_optimize(self, test_periods=30):
|
||||
"""
|
||||
通过回测优化各模型权重
|
||||
使用简单网格搜索 + 梯度上升混合方法
|
||||
"""
|
||||
rule = self.rule
|
||||
rmax, bmax = self.rule["red_max"], self.rule["blue_max"]
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🧠 集成学习权重优化 | 彩种: {rule['name']} | 数据期数: {len(self.data)}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# 准备回测数据(按时间顺序)
|
||||
sorted_data = sorted(self.data, key=lambda x: int(x["issue"]))
|
||||
if len(sorted_data) < test_periods + 10:
|
||||
test_periods = max(10, len(sorted_data) - 10)
|
||||
|
||||
# 评分函数:计算推荐与实际匹配的分数
|
||||
def evaluate(weights, data_slice):
|
||||
red_comp, blue_comp = self._get_combined_for_weights(weights)
|
||||
pred_r, pred_b = self._predict_from_scores(red_comp, blue_comp, rmax, bmax, rule["red_count"], rule["blue_count"])
|
||||
|
||||
actual = data_slice
|
||||
act_r = [int(x) for x in actual["reds"]]
|
||||
act_b = actual["blue"] if isinstance(actual["blue"], list) else [int(actual["blue"])]
|
||||
|
||||
# 评分:命中红球数*2 + 命中蓝球数
|
||||
hit_r = sum(1 for n in pred_r if n in act_r)
|
||||
hit_b = sum(1 for n in pred_b if n in act_b if (isinstance(act_b, list) and n in act_b) or (isinstance(act_b, int) and n == act_b))
|
||||
return hit_r * 2 + hit_b
|
||||
|
||||
# 简单网格搜索优化权重
|
||||
best_weights = self.weights.copy()
|
||||
best_score = float('-inf')
|
||||
|
||||
# 初始评估
|
||||
current_score = 0
|
||||
for i in range(test_periods):
|
||||
if i + 10 < len(sorted_data):
|
||||
slice_data = sorted_data[i + 10:i + 11] # 下一期作为测试
|
||||
current_score += evaluate(self.weights, slice_data[0])
|
||||
|
||||
print(f" 初始等权总分: {current_score:.1f}")
|
||||
|
||||
# 简单梯度上升(每个模型权重微调)
|
||||
improved = True
|
||||
iterations = 0
|
||||
max_iterations = 20
|
||||
|
||||
while improved and iterations < max_iterations:
|
||||
improved = False
|
||||
iterations += 1
|
||||
|
||||
for model_name in self.weights:
|
||||
for delta in [-0.05, 0.05]: # 微调权重
|
||||
test_w = self.weights.copy()
|
||||
test_w[model_name] = max(0.01, min(0.9, test_w[model_name] + delta))
|
||||
|
||||
# 归一化
|
||||
total = sum(test_w.values())
|
||||
test_w = {k: v/total for k, v in test_w.items()}
|
||||
|
||||
score = 0
|
||||
for i in range(test_periods):
|
||||
if i + 10 < len(sorted_data):
|
||||
slice_data = sorted_data[i + 10:i + 11]
|
||||
score += evaluate(test_w, slice_data[0])
|
||||
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_weights = test_w.copy()
|
||||
improved = True
|
||||
|
||||
self.weights = best_weights
|
||||
self.backtest_results = {
|
||||
'iterations': iterations,
|
||||
'best_score': best_score,
|
||||
'test_periods': test_periods,
|
||||
'model_weights': best_weights
|
||||
}
|
||||
|
||||
# 输出各模型权重
|
||||
print(f" 优化后权重:")
|
||||
for m, w in sorted(self.weights.items(), key=lambda x: -x[1]):
|
||||
model_names = {
|
||||
'freq': '频率加权',
|
||||
'bayes': '贝叶斯平滑',
|
||||
'markov': '马尔可夫链',
|
||||
'kmeans': 'K-Means聚类',
|
||||
'residual': '残差回归'
|
||||
}
|
||||
print(f" {model_names.get(m, m):<12s}: {w:.4f}")
|
||||
|
||||
print(f" 最终集成总分: {best_score:.1f} (测试{test_periods}期)")
|
||||
|
||||
# 返回综合评分
|
||||
return self.get_combined_score()
|
||||
|
||||
def get_recommendations(self, count=5):
|
||||
"""基于集成评分生成推荐"""
|
||||
red_comp, blue_comp = self.get_combined_score()
|
||||
rmax, bmax = self.rule["red_max"], self.rule["blue_max"]
|
||||
rcnt, bcnt = self.rule["red_count"], self.rule["blue_count"]
|
||||
|
||||
sorted_r = sorted(red_comp.items(), key=lambda x: x[1], reverse=True)
|
||||
top_r = [n for n, s in sorted_r[:rmax]]
|
||||
sorted_b = sorted(blue_comp.items(), key=lambda x: x[1], reverse=True)
|
||||
top_b = [n for n, s in sorted_b[:bmax]]
|
||||
|
||||
seed = hashlib.md5(f"{self.lt}_ensemble".encode()).hexdigest()
|
||||
res = []
|
||||
for ci in range(count):
|
||||
cs = hashlib.md5(f"ensemble_c{ci}_{seed}".encode()).hexdigest()
|
||||
cr = random.Random(cs)
|
||||
|
||||
sel = []
|
||||
cand = top_r[:]
|
||||
w = [red_comp.get(n, 0.5) for n in cand]
|
||||
while len(sel) < rcnt and cand:
|
||||
tw = sum(w)
|
||||
if tw <= 0: pk = cr.choice(cand)
|
||||
else:
|
||||
rv = cr.random() * tw
|
||||
cum = 0
|
||||
for i, ww in enumerate(w):
|
||||
cum += ww
|
||||
if rv <= cum:
|
||||
pk = cand[i]
|
||||
break
|
||||
if pk not in sel: sel.append(pk)
|
||||
idx = cand.index(pk); cand.pop(idx); w.pop(idx)
|
||||
sel.sort()
|
||||
|
||||
bls = []
|
||||
cand_b = top_b[:]
|
||||
wb = [blue_comp.get(n, 0.5) for n in cand_b]
|
||||
while len(bls) < bcnt and cand_b:
|
||||
tw = sum(wb)
|
||||
if tw <= 0: pk = cr.choice(cand_b)
|
||||
else:
|
||||
rv = cr.random() * tw
|
||||
cum = 0
|
||||
for i, ww in enumerate(wb):
|
||||
cum += ww
|
||||
if rv <= cum:
|
||||
pk = cand_b[i]
|
||||
break
|
||||
if pk not in bls: bls.append(pk)
|
||||
idx = cand_b.index(pk); cand_b.pop(idx); wb.pop(idx)
|
||||
bls.sort()
|
||||
|
||||
score = round(sum(red_comp.get(n, 0) for n in sel) + sum(blue_comp.get(n, 0) for n in bls), 4)
|
||||
mid = rmax / 2
|
||||
res.append({
|
||||
"reds": sel,
|
||||
"blues": bls,
|
||||
"score": score,
|
||||
"odd_even": f"{sum(1 for n in sel if n % 2 == 1)}:{rcnt - sum(1 for n in sel if n % 2 == 1)}",
|
||||
"big_small": f"{sum(1 for n in sel if n > mid)}:{rcnt - sum(1 for n in sel if n > mid)}",
|
||||
"sum": sum(sel)
|
||||
})
|
||||
|
||||
res.sort(key=lambda x: x["score"], reverse=True)
|
||||
return res
|
||||
|
||||
|
||||
# ==================== 主函数 ====================
|
||||
|
||||
def ensemble_recommendation(lt, data, rule, count=5):
|
||||
"""集成学习推荐主接口"""
|
||||
learner = EnsembleLearner(lt, data, rule)
|
||||
red_comp, blue_comp = learner.backtest_optimize(test_periods=30)
|
||||
recommendations = learner.get_recommendations(count=count)
|
||||
|
||||
return {
|
||||
'weights': learner.weights,
|
||||
'backtest': learner.backtest_results,
|
||||
'recommendations': recommendations,
|
||||
'combined_scores': {
|
||||
'red': red_comp,
|
||||
'blue': blue_comp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 简单测试
|
||||
print("集成学习模块加载成功!")
|
||||
print("可用模型:频率加权、贝叶斯平滑、马尔可夫链、K-Means聚类、残差回归")
|
||||
Reference in new issue
Block a user