feat: 彩票智能分析技能 v1.0 - 双色球/大乐透集成学习模型

This commit is contained in:
CodeForge committed 2026-07-15 02:56:14 +00:00
commit 56b02238ea
18 files changed
+8230

No files matched your search

+725
View File
@@ -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聚类、残差回归")