#!/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聚类、残差回归")