首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于 TensorFlow.js + LSTM + CRF 的双色球序列模型探索

基于 TensorFlow.js + LSTM + CRF 的双色球序列模型探索

原创
作者头像
飞猫警长
修改于 2026-10-07 21:28:26
修改于 2026-10-07 21:28:26
40
举报

彩票的随机性是不可动摇的前提

在开始任何技术讨论之前,必须先说清楚一件事:双色球开奖是独立随机事件。 每一期开奖结果与之前任何一期都没有因果关系,历史数据不包含任何可用于预测未来的信息。

这意味着本文讨论的所有模型,都不具备预测中奖号码的能力。如果有人告诉你某个模型“能预测彩票”,那一定是骗局。

那为什么还要做这件事?因为彩票数据是一个天然的、高质量的、公开的随机序列数据集。用它来探索序列模型的行为——看模型如何在纯噪声上“强行寻找规律”、如何过拟合、CRF 层如何建模标签依赖——这本身是一个极好的机器学习练习。理解模型在随机数据上的失败,比理解它在规律数据上的成功更有价值。

以下是我们的技术探索记录。

一、为什么选 TensorFlow.js

传统的做法是在服务端用 Python + TensorFlow 训练模型,前端只负责展示结果。但这带来两个问题:一是需要服务器和 GPU 成本,二是用户数据要上传到服务器。

TensorFlow.js 让整个训练过程在浏览器本地完成。用户打开网页,模型在本地初始化、训练、推理,数据不离开浏览器。对于彩票这种敏感话题,本地训练在隐私和合规上都更安全。

代价是性能。浏览器端没有 CUDA,只能依赖 WebGL 或 WASM 后端,训练速度大约是服务端的十分之一到五分之一。但对于几百到几千条数据的序列模型,这个速度可以接受。

二、问题建模:把选号变成序列标注

双色球每期开出 6 个红球(1-33)和 1 个蓝球(1-16)。如果把它看作一个序列标注任务,可以这样定义:

输入序列:最近 N 期的开奖号码(N 是窗口大小,比如 50 期)

输出标签:下一期每个位置的号码

这里有个关键设计:红球的 6 个位置是无序的,但模型需要一个固定顺序。一种常见的处理是先排序再建模——把每期红球从小到大排列,位置 1 就是最小的号码,位置 6 是最大的号码。

这样就变成了一个典型的序列到序列问题:输入一个长度为 N 的历史序列,输出一个长度为 6 的红球序列和一个长度为 1 的蓝球序列。

三、LSTM 层:捕捉序列中的时序依赖

LSTM(长短期记忆网络)是处理序列数据的经典结构。它通过输入门、遗忘门、输出门三个机制,决定哪些信息该记住、哪些该遗忘。

在我们的模型中,LSTM 层的作用是:

  1. 编码历史信息:把最近 N 期的号码序列编码成一个隐藏状态向量
  2. 捕捉模式:尝试学习号码之间的“趋势”——即使这些趋势在随机数据中并不存在

代码上,用 TensorFlow.js 构建一个双层 LSTM 大致是这样:

代码语言:javascript
复制
const model = tf.sequential();

model.add(tf.layers.lstm({
    units: 128,
    returnSequences: true,
    inputShape: [windowSize, featureCount],
}));

model.add(tf.layers.dropout({ rate: 0.2 }));

model.add(tf.layers.lstm({
    units: 128,
    returnSequences: false,
}));

model.add(tf.layers.dense({
    units: 6 * 33, // 6 个位置,每个位置 33 个候选号码
    activation: 'softmax',
}));

注意最后的 dense 层输出维度是 6 × 33 = 198。这意味着我们把 6 个位置的预测独立处理——每个位置都做一个 33 类的 softmax 分类。

但这里有个问题:6 个位置的预测是独立的,模型可能会让两个位置预测出同一个号码。在实际彩票中,6 个红球不会重复。这就需要 CRF 层来解决。

四、CRF 层:建模标签之间的依赖关系

CRF(条件随机场)通常用于序列标注任务(比如命名实体识别),它的核心价值是建模标签之间的转移概率。

在我们的场景中,CRF 可以解决两个问题:

  1. 位置间的依赖:第 2 个位置的号码不应该和第 1 个位置相同
  2. 全局最优:不是每个位置独立取最大概率,而是找到整个序列的全局最优解

标准的 CRF 层在 TensorFlow.js 中没有官方实现,需要自己构建。一个简化版的思路是:

代码语言:javascript
复制
// 伪代码:CRF 层的核心逻辑
// 1. 计算每个位置的发射分数(来自 LSTM 的输出)
// 2. 计算标签之间的转移分数(可学习的参数矩阵)
// 3. 用 Viterbi 算法解码出全局最优路径

class CRFLayer {
    constructor(numLabels) {
        // 转移矩阵:从标签 i 转移到标签 j 的分数
        this.transitions = tf.variable(
            tf.zeros([numLabels, numLabels])
        );
    }

    // 计算最优路径
    decode(emissions) {
        // Viterbi 算法
        // ...
    }
}

在实际实现中,因为双色球的位置约束(号码递增),我们可以简化转移矩阵的设计:只允许从较小的号码转移到较大的号码,这样天然避免了重复。

但说实话,在浏览器端用 JavaScript 实现完整的 CRF 推理(包括前向-后向算法和 Viterbi 解码)计算量不小。一个更实际的方案是用约束替代 CRF:在 softmax 输出之后,加一层掩码(mask),把已经选过的号码概率置零,然后重新归一化。

五、模型训练与评估

数据准备

从公开渠道获取双色球历史数据,每期 6 个红球 + 1 个蓝球。按时间顺序切分,前 80% 作为训练集,后 20% 作为验证集。

训练配置

  • 窗口大小:50 期
  • 批次大小:32
  • 训练轮数:50
  • 优化器:Adam
  • 损失函数:分类交叉熵

评估指标

因为彩票是随机的,传统的“准确率”指标没有意义——模型永远不可能预测对。更有意义的评估是:

  1. 训练损失 vs 验证损失:看模型是否过拟合
  2. 预测分布的熵:模型输出的概率分布是均匀的(接近随机)还是尖锐的(有偏好)
  3. 命中数分布:统计模型预测的号码与实际开奖号码的重合个数,看是否偏离随机基线

关键观察:如果模型在验证集上的损失不再下降,甚至开始上升,而训练损失继续下降,说明模型在记忆训练数据中的噪声。这是随机数据上的典型表现,也证明了彩票数据中不存在可学习的规律。

六、结论:模型学到了什么

经过多次实验,我们的观察是:

  1. 模型能收敛,但收敛到的是噪声。训练损失可以降到很低,但验证损失在几个 epoch 后就停止下降。
  2. 预测分布接近均匀。一个好的模型在随机数据上应该输出接近均匀的概率分布,这恰恰说明它“学会了随机性”。
  3. 命中数符合超几何分布。模型预测的号码与实际开奖号码的重合个数,和随机选号的重合个数没有统计显著差异。
  4. CRF 或约束层能改善“合法性”。加上去重约束后,模型输出的号码组合更“像”真实开奖号码(6 个不重复、有序),但这只是形式上的改善,不代表预测能力。

最重要的结论:这个过程让我们直观地看到了神经网络在纯噪声数据上的行为。它不会“发现规律”,只会“拟合噪声”。理解这一点,比得到一个高准确率的模型更有价值。

七、这个项目的实际价值

虽然模型不能预测彩票,但这个项目本身有实际价值:

  • 技术演示:展示了如何在浏览器端完成一个完整的序列建模流程
  • 教学工具:让用户直观感受 LSTM、Transformer、CRF 的工作原理
  • 可视化:概率分布图、热力图让抽象的模型输出变得可理解
  • 反面教材:证明了“用 AI 预测彩票”是伪命题

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、为什么选 TensorFlow.js
  • 二、问题建模:把选号变成序列标注
  • 三、LSTM 层:捕捉序列中的时序依赖
  • 四、CRF 层:建模标签之间的依赖关系
  • 五、模型训练与评估
    • 数据准备
    • 训练配置
    • 评估指标
  • 六、结论:模型学到了什么
  • 七、这个项目的实际价值
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档