YAOTU INSIGHTS

用机器学习分析双色球:从数据清洗到特征工程的完整实践

用机器学习分析双色球:从数据清洗到特征工程的完整实践
简介面向机器学习入门者与数据预测爱好者的实战资料包以“采用机器学习分析双色球”为切入点完整呈现从数据收集、清洗、独热编码、特征工程到模型训练、交叉验证与参数调优的端到端流程。资源共29个文件以16个Python脚本为核心涵盖TensorFlow、PyTorch、NumPy等框架的示例代码并配有网络爬虫脚本、SQL导入导出工具、配置文件、运行日志和README说明压缩包仅181KB轻量便携。包内多个版本脚本可对比不同框架的建模思路事件日志记录训练过程适合动手复现。已有383人学习/浏览。通过研读代码可掌握多模型对比、冷热号统计与红蓝球分类预测的落地写法同时理解彩票高随机性下的模型局限适合在机器学习课程或业余项目中作趣味性实战参考。1. 机器学习分析双色球先搞清楚我们到底在预测什么打开这个 zip 的时候我猜你已经看过里面的目录结构了一份历史开奖 CSV、一个训练脚本、一个预测脚本外加一个写得半通不通的 README。用机器学习分析双色球听起来像玄学但把它拆开来看其实是一个很标准的机器学习入门项目——数据处理、特征工程、模型训练、评估验证全套流程都能练到。需要先把丑话说在前面双色球开奖是独立随机事件模型真正能学到的不是“下一期号码”而是号码在历史样本里的分布形态、遗漏周期、和值区间这类统计规律。这篇文章我会从 zip 里的原始数据开始一步步带你把它变成可训练的样本再训练模型、评估效果最后把脚本做成每周能自动跑的流程。2. 数据准备从 zip 解压到构造可训练的样本2.1 解压后先确认数据字段别急着跑训练脚本我拿到这个 zip 后做的第一件事不是双击解压然后立刻 python train.py而是先看数据长什么样。常见的历史开奖数据是这样一份 CSV期号开奖日期红1红2红3红4红5红6蓝球20240012024-01-0251217232831720240022024-01-04291422263312打开之前先做三件事确认编码、确认列名、确认号码范围。我习惯用 pandas 直接读编码优先试 utf-8-sigWindows 导出的 CSV 十有八九是 GBK 或带 BOM 的 UTF-8直接 utf-8 读会乱码。号码范围也必须跑一遍断言红球必须在 1 到 33 之间蓝球必须在 1 到 16 之间超出这个范围的直接作为脏数据处理。import pandas as pd df pd.read_csv(data/balls.csv, encodingutf-8-sig, parse_dates[开奖日期]) df df.sort_values(开奖日期).reset_index(dropTrue) red_cols [f红{i} for i in range(1, 7)] assert df[red_cols].min().min() 1, 红球出现小于1的非法值 assert df[red_cols].max().max() 33, 红球出现大于33的非法值 assert df[蓝球].between(1, 16).all(), 蓝球出现非法值 df df.drop_duplicates(subset[期号], keeplast).reset_index(dropTrue) print(df.shape) print(df.dtypes)这段代码做的事情很直白按开奖日期排序、校验号码范围、按期号去重。逻辑说明排序是为了后续滑窗切分时保证时间顺序正确掉重是因为有些来源的数据会重复收录同一期。参数说明encodingutf-8-sig 是读取带 BOM 的 UTF-8 文件的关键换成 utf-8 时第一列列名会带上 \ufeffparse_dates 让日期列直接变成 datetime 类型后面算时间跨度不用再手动 to_datetime。2.2 清洗异常行停售期、缺失值和杂讯双色球历史上不是每期都正常开奖春节休市、系统故障、数据源拼接错位都会产生脏行。清洗时我按这个顺序来先砍掉红球列或蓝球列有缺失的行再检查红球是否有重复号码——一注里 6 个红球不能重复出现重复行八成是数据源拼接的时候把两行并成一行了直接删。最后看期号是否连续递增如果中间跳号严重说明数据源缺了整段这时候要回去重新找数据别靠插值补。df df.dropna(subsetred_cols [蓝球]) def has_duplicate_red(row): return len(set(row[red_cols].values)) ! 6 df df[~df.apply(has_duplicate_red, axis1)].reset_index(dropTrue) print(f清洗后剩余 {len(df)} 期数据)清洗完之后还要顺手算几个基础统计列后面做特征的时候要反复用红球和值、红球跨度、奇偶比、区间比。这些是每期开奖号码的“当期画像”单独看没意义但放在窗口里取均值、取最近值就是能喂给模型的特征。df[红球和值] df[red_cols].sum(axis1) df[红球跨度] df[red_cols].max(axis1) - df[red_cols].min(axis1) df[红球奇偶比] (df[red_cols] % 2).sum(axis1) df[红球区间比] (df[red_cols] 11).sum(axis1).astype(str) - \ ((df[red_cols] 11) (df[red_cols] 22)).sum(axis1).astype(str) - \ (df[red_cols] 22).sum(axis1).astype(str)逻辑说明红球和值即当期 6 个红球之和常见分布区间在 63 到 183 之间跨度是最大号减最小号奇偶比是奇数个数区间比把 1-33 分成三段统计每段各出几个。这四个量不是给模型直接预测的而是用来刻画出号形态。2.3 滑窗法把开奖序列切成监督学习样本机器学习模型不吃序列吃的是“特征标签”的表格。滑窗是处理这类时间序列最朴素也最稳妥的做法对第 t 期取它之前 30 期的数据算出特征标签取第 t 期实际开奖结果。这样每个样本就是“过去 30 期长什么样 → 下一期出了什么号”。这里有个关键选择直接预测 6 个红球的具体数字是回归问题模型很难收敛更常见的做法是把问题转成“每个号码在下一期是否出现”的二分类。红球有 33 个候选号码每个号码对每一期都生成一条样本特征里带上号码本身的统计量标签就是 0 或 1。这样样本量瞬间放大 33 倍2000 期历史数据就能得到 6 万条训练样本足够喂饱一个树模型。import numpy as np def make_red_ball_samples(df, window30): rows [] red_arr df[red_cols].values # shape: (n_periods, 6) sums df[红球和值].values spans df[红球跨度].values odd_counts df[红球奇偶比].values for t in range(window, len(df)): win_red red_arr[t-window:t] # 窗口内所有红球 for n in range(1, 34): occur np.argwhere(win_red n) last_gap window - occur[-1][0] if len(occur) 0 else window freq_30 (win_red n).sum() / (window * 6) rows.append({ 期号: df[期号].iloc[t], 号码: n, 遗漏值: last_gap, 近30期频率: freq_30, 窗口和值均值: sums[t-window:t].mean(), 窗口跨度均值: spans[t-window:t].mean(), 窗口奇偶比均值: odd_counts[t-window:t].mean(), label: 1 if n in red_arr[t] else 0 }) return pd.DataFrame(rows) samples make_red_ball_samples(df, window30) print(samples[label].mean())逻辑说明外层循环遍历每一期对每个候选号码 1 到 33 分别计算它的特征。遗漏值表示这个号码距离窗口末端也就是第 t-1 期已经多少期没出现过这是彩票分析里最有价值的特征之一近 30 期频率是它在这 30 期里出现的密度用来区分热号和冷号。窗口和值均值、跨度均值、奇偶比均值刻画的是近期整体出号形态。参数说明window30 表示回看 30 期这个值可以根据数据量调历史数据超过 1500 期时window 取 30 或 50 效果差别不大但 window 太大会让遗漏值失去区分度。3. 特征工程给模型喂这 6 类号球特征3.1 单期统计特征和值、跨度、奇偶比、区间比双色球分析里最常用的几类手工特征是开奖号码的统计形态。和值高说明这期偏向出大号跨度大说明号码分布很散奇偶比失衡说明单双号不均。这些特征单独看是开奖结果的一部分但放进窗口里取均值、取趋势就成了描述近期形态的时序信号。我一般在窗口上算三组统计量最近一期的原始值、窗口内均值、窗口内标准差。原始值刻画当下状态均值刻画整体水平标准差刻画波动性。比如红球和值近 30 期均值如果一直在 110 以上说明近期偏大号标准差突然拉大说明号球分布不稳定可能后续会回归。这类特征不需要做复杂编码数值型直接进模型就行。samples[窗口和值标准差] samples.groupby(期号)[窗口和值均值].transform(lambda x: x.rolling(5).std())这里我补充了一个 5 期滚动标准差理由是彩票走势本身没有强规律但滚动的波动率能帮树模型捕捉到“形态正在变化”的信号。需要注意时效性——特征只能用到第 t-1 期为止的数据绝不能把第 t 期的和值、跨度算进特征里这是典型的未来函数后面避坑章节还会专门展开。3.2 跨期衍生特征遗漏值、热冷强度和重复率把号码当独立个体看最重要的特征就是遗漏值和近 N 期频率。遗漏值指“这个号码已经连续多少期没出现”理论上是几何分布均值约 5 期但实际序列经常出现 15 期以上的长遗漏这时候号码的遗漏值就成了一个强信号。热冷强度可以把近 30 期频率做标准化把频率除以 6/33 的理论概率大于 1 是热号小于 1 是冷号。theoretical_prob 6 / 33 samples[热冷强度] samples[近30期频率] / theoretical_prob samples[遗漏值归一化] samples[遗漏值] / 30.0参数说明热冷强度的分母是双色球红球单期理论出现概率任何号码长期看都收敛到 6/33偏离这个值只是短期统计波动。遗漏值归一化到 0 到 1 之间对树模型来说不影响分裂但如果之后要接逻辑回归或神经网络这一步就很有必要。重复率是另一个值得加的特征看当前窗口里有没有号码重复出现以及重复的密集度。具体做法是统计窗口内 30×6 个红球位置里出现次数排名前 5 的号码各自出现了几次。这个特征的思想是号码的开出存在短期的聚集效应虽然不规律但树模型可以自己决定要不要用这个信号。3.3 特征归一化与保存特征清单到这里特征维度差不多有 8 到 10 列足够训练了。归一化对 LightGBM 这类树模型不是必须的但如果后续要对比逻辑回归就得把数值特征都做标准化。我更推荐的做法是把特征列名存成一个列表连同 model 一起保存。这个习惯能避免后面载入模型做预测时列顺序错乱——这个问题一旦出现预测结果基本都是乱序概率排查起来非常痛苦。feature_cols [号码, 遗漏值, 近30期频率, 热冷强度, 窗口和值均值, 窗口跨度均值, 窗口奇偶比均值, 窗口和值标准差] X samples[feature_cols] y samples[label]逻辑说明号码本身作为特征很重要。模型需要知道它现在在评估的是 1 号到 33 号里的哪一个号码因为不同号码在不同时期的遗漏分布、频率特性都不完全一样。如果不把号码编码进去等于强迫模型对所有候选号码一视同仁效果会大打折扣。参数说明特征列表里没有把期号放进去因为期号是索引不是信号。4. 模型选择与训练用 LightGBM 给红球出现概率排序4.1 为什么不用 LSTM 直接预测号码很多人一看到时序数据就想上 LSTM 或 Transformer这在双色球场景里属于杀鸡用牛刀而且大概率翻车。原因很现实LSTM 需要的核心前提是序列里存在可学习的长程依赖而双色球开奖是独立随机事件序列的自相关性几乎为零。硬把几千期号码灌进 LSTM模型只能把历史噪声背下来验证集上表现和随机没差别。LightGBM 这类梯度提升树更适合这个场景它对表格特征的处理非常高效能自动组合特征训练速度快还能输出特征重要性方便我们事后检查模型到底学了什么。import lightgbm as lgb from sklearn.metrics import roc_auc_score split_point int(len(samples) * 0.8) X_train, X_val X.iloc[:split_point], X.iloc[split_point:] y_train, y_val y.iloc[:split_point], y.iloc[split_point:] model lgb.LGBMClassifier( objectivebinary, learning_rate0.05, num_leaves31, max_depth-1, min_data_in_leaf20, feature_fraction0.8, bagging_fraction0.8, bagging_freq5, n_estimators300, random_state42 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], eval_metricauc, callbacks[lgb.early_stopping(50), lgb.log_evaluation(50)] )逻辑说明这里用的是单个二分类 LightGBM 模型对所有 (期号, 号码) 样本统一训练。特征里带上了号码本身所以模型能学到不同号码在不同状态下的出现倾向。eval_metric 用 AUC 而不是准确率是因为正负样本比例约 6/33准确率在类别不平衡下会虚高。参数说明learning_rate0.05 让模型学得慢但稳num_leaves31 控制了树的复杂度数据信号弱时叶子数太多容易过拟合feature_fraction 和 bagging_fraction 都设为 0.8相当于给列和行都做采样进一步压制方差。4.2 时间序列切分不能随机 shuffle训练集和验证集的划分在这一步必须格外小心。很多人在 sklearn 里习惯了 train_test_split上来就随机切这在双色球场景里是致命的。原因很简单我们的特征是滑窗构造的相邻两期样本的窗口高度重叠随机切分会让训练集里混入验证集的“邻居样本”信息从窗口缝隙里漏过去验证指标必然虚高。正确做法是按时间顺序切分拿前 80% 的历史做训练后 20% 做验证。上面的代码里 split_point int(len(samples) * 0.8) 就是按时间比例硬切因为样本是按期号顺序排列的。如果你需要更严谨的验证可以用 sklearn 的 TimeSeriesSplit 做 K 折但每一折的训练集都必须严格在验证集之前。另一种做法是直接按日期切比如 2022 年之前的数据训练2023 年至今的数据验证这样更接近真实的使用场景。4.3 预测下一期号码概率排序取 Top 6模型训练完成后预测时的逻辑和训练时要完全一致。对最新一期之前的 window 期数据构造同样的特征生成 33 条候选样本每条样本对应一个红球号码用模型预测每个号码的出现概率最后按概率从大到小排序取前 6 个作为模型给出的参考红球。def build_latest_samples(df, window30): rows [] t len(df) red_arr df[red_cols].values sums df[红球和值].values spans df[红球跨度].values odd_counts df[红球奇偶比].values win_red red_arr[t-window:t] for n in range(1, 34): occur np.argwhere(win_red n) last_gap window - occur[-1][0] if len(occur) 0 else window freq_30 (win_red n).sum() / (window * 6) rows.append({ 号码: n, 遗漏值: last_gap, 近30期频率: freq_30, 热冷强度: freq_30 / (6 / 33), 窗口和值均值: sums[t-window:t].mean(), 窗口跨度均值: spans[t-window:t].mean(), 窗口奇偶比均值: odd_counts[t-window:t].mean(), 窗口和值标准差: sums[t-window:t].std() }) return pd.DataFrame(rows) latest build_latest_samples(df, window30) prob model.predict_proba(latest[feature_cols])[:, 1] ranking np.argsort(-prob) top6_red sorted([int(i) 1 for i in ranking[:6]]) print(模型参考红球:, top6_red)逻辑说明build_latest_samples 的窗口取的是从倒数第 30 期到最后一期的数据正好不包含待预测的那一期。对每个候选号码计算遗漏值、频率等特征后预测概率并排序。参数说明预测时的 window 必须和训练时一致否则特征分布对不上如果训练时得过热冷强度预测时也必须算同样的列。5. 避坑与评估5 个让双色球模型结果失真的排查点5.1 特征用了当期数据训练准确率 95%开奖命中率接近随机这是双色球机器学习项目里最容易犯、也最难排查的错误。现象是训练集 AUC 高得离谱验证集也还行但拿去预测下一期命中率跟瞎猜没什么区别。原因几乎都是特征构造时引入了当期信息典型的例子是用前后文滑窗算遗漏值时把 t 期本身的号码也写进了窗口或者算窗口和值时不小心把 t 期包含进去。解决方法是自查特征构造函数确保每行样本只用到 t-window 到 t-1 期的数据然后写一个断言随机挑几期样本手动改掉窗口里最后一期的号码看特征值是否变化如果变了说明特征里混入了当期数据。5.2 随机切分训练集模型学会了“剧透”现象是第一次训练时验证 AUC 0.72兴冲冲上线预测结果却完全不对。原因前面讲过滑窗样本之间高度重叠随机切分等于把未来的窗口片段嵌进了训练集。解决方法是强制按时间切分并且记录切分的日期边界。我习惯把验证指标差的模型先留下等几期真实开奖后拿真实结果验证模型的排序效果这比任何验证集指标都可信。5.3 导入资源包失败 caused by: invalid zip archive: could not find eocd这个报错我遇到过不止一次而且经常是在别人发给我的数据包里。现象是解压工具报错或者 unzip 命令直接提示 invalid zip archive: could not find eocd。原因是 zip 的 EOCD中央目录结束标记位于文件末尾下载过程中断、剪贴板传送截断、杀毒软件隔离都会让文件末尾缺失数据导致解压工具找不到 EOCD 记录。解决方法是先校验文件完整性用ls -l看文件大小是否合理再用 hexdump 检查文件末尾是否有 EOCD 标记PK\x05\x06。没有的话别硬解找发件人重新发一份或者用 7-Zip 打开测试压缩包是否损坏。5.4 Windows 下中文列名乱码、中文路径保存模型失败现象是 pandas 读 CSV 后列名显示乱码或者 LightGBM 的模型保存到带中文的目录时报错。原因有两个层面CSV 编码不是 UTF-8是 GBK 或带 BOM 的 UTF-8Windows 控制台默认代码页和 Python 字符串编码不一致导致模型文件写入路径时编码爆炸。解决方法是读取时统一用 encodingutf-8-sig同时把结果文件和模型都保存到纯英文路径下。一个小习惯所有数据文件先转成 UTF-8 规范再入库别把原始编码带到后续流程里。5.5 特征列顺序不一致预测概率全错位现象是模型加载旧权重后predict_proba 输出的概率排序看起来有问题比如冷号全排前面。原因是训练时的 feature_cols 和预测时的特征 DataFrame 列顺序不一致LightGBM 按位置取特征列名对不上时不会报错只会默默用错数据。解决方法是训练完立刻把模型自带的 feature_name 保存下来feature_names list(model.booster_.feature_name()) with open(feature_names.txt, w, encodingutf-8) as f: f.write(\n.join(feature_names))预测前再读出来对预测特征做 reorder。这个动作几十秒能省掉后续大量排查时间。5.6 用随机基线对照判断模型到底有没有用双色球模型必须回答一个问题模型输出是不是比随机抽号强。随机基线很好算从 33 个红球里随机抽 6 个期望命中数 6 × 6/33 ≈ 1.09也就是平均每期命中 1 个红球。如果你的模型预测的 Top 6 红球平均命中数长期在 1.5 以下说明它几乎没有学到任何有效信号。我每季度会做一次蒙特卡洛模拟随机抽 1 万组作为基线同时统计模型近 30 期的平均命中数两边一比就能客观判断模型是否还在工作。这个方法也能帮你在早期就识别出特征失效而不是等到连续几期不中才怀疑模型。6. 落地技巧把分析脚本做成每周可复用流程6.1 用定时任务替代手工跑脚本模型训练好、特征管线稳定之后剩下的就是把流程自动化。我习惯把训练和预测拆成两个脚本train_model.py 每周日跑一次读取最新开奖数据重新训练并保存模型predict_next.py 在每期开奖前跑一次输出参考号码。定时任务用系统自带的 cron 或 Windows 任务计划程序就能搞定不需要引入额外框架。# 每周日 20:00 重新训练模型 0 20 * * 0 cd /path/to/lottery python train_model.py logs/train.log 21 # 每周二、四、日 20:30 预测下一期 30 20 * * 2,4,7 cd /path/to/lottery python predict_next.py logs/predict.log 21逻辑说明训练放在每周日是因为数据更新以周为粒度足够预测放在开奖日当天避免隔太久数据过期。参数说明日志重定向一定要写脚本一旦跑挂日志是唯一的排查入口。6.2 长期记录预测结果只看命中率曲线做预测不记录结果等于白做。我维护一个 evaluation.csv每期预测完把 Top 6 号码和实际开奖号码追加进去隔几个月就统计一次累计命中率并和随机基线对比。如果连续 30 期的平均命中数跌破随机基线不要急着调参先回去查特征是不是失效了或者数据源是不是有新的脏数据。这套习惯帮我避开了很多“模型训练时好用时不好用”的玄学问题。我自己的习惯是每个季度只重训一次模型中间只做预测和记录不频繁调参。彩票数据本身就充满噪声调参调得太频繁只会让模型记住上一段噪声而不是学到稳定的统计形态。更重要的是我一直把它当成机器学习练习而不是收益手段——模型给出的号码只做统计参考不构成投注建议。理性看待随机事件才能把精力放在真正有积累的事上。希望帮到你。本文还有配套的精品资源点击获取