AI数学基础(三):概率与统计 —— Java 实现均值/方差/协方差计算

——别让“平均值”掩盖了数据的真实波动

大家好,我是那个总在监控图表里看标准差、又在 A/B 测试报告里算 p 值的老架构。今天不聊神经网络,也不谈梯度下降——我们回到 AI 的另一块基石:

当你从电科金仓 KingbaseES(KES)中读出一组用户点击率、响应延迟或 embedding 范数,除了看“平均值”,你还该看什么?

很多人汇报:“本周平均点击率 5.2%”,然后结束。
但如果你不知道这组数据的方差有多大,就不知道这个“5.2%”是稳定表现,还是由少数极端值拉高的假象。

在 AI 工程中,概率与统计不是理论课,而是决策的依据。模型是否漂移?特征是否有效?实验是否显著?答案都在数据的分布里。

今天我们就用 Java,手写均值、方差、协方差的实现,并展示如何用它们构建一个实时特征健康度监控系统


一、为什么统计量对 AI 至关重要?

AI 系统不是静态的,它活在动态数据流中。而统计量,是你感知“变化”的眼睛:

  • 均值(Mean):中心趋势;
  • 方差(Variance):波动程度;
  • 协方差(Covariance):特征间关联性;
  • 标准差(StdDev):异常检测阈值。

举个真实场景:某银行用 KES 存储用户交易 embedding。某天模型效果突降,排查发现:embedding 的 L2 范数均值未变,但方差飙升——说明新数据分布混乱,可能因埋点 bug 引入噪声。

而这一切,只需几行统计代码就能预警。


二、Java 实现:从单变量到多变量

1. 单变量统计:均值与方差

public class UnivariateStats {
    public static double mean(double[] data) {
        return Arrays.stream(data).average().orElse(0.0);
    }

    // 样本方差(分母 n-1)
    public static double variance(double[] data) {
        if (data.length < 2) return 0.0;
        double mean = mean(data);
        double sum = Arrays.stream(data)
                           .map(x -> (x - mean) * (x - mean))
                           .sum();
        return sum / (data.length - 1);
    }

    public static double stdDev(double[] data) {
        return Math.sqrt(variance(data));
    }
}

✅ 使用 double 而非 float,避免精度损失(尤其在方差计算中)。


2. 双变量统计:协方差

协方差衡量两个变量是否“同向变化”。正值表示正相关,负值表示负相关。

public class BivariateStats {
    public static double covariance(double[] x, double[] y) {
        if (x.length != y.length || x.length < 2) {
            throw new IllegalArgumentException("维度不匹配");
        }
        double meanX = UnivariateStats.mean(x);
        double meanY = UnivariateStats.mean(y);
        
        double sum = 0.0;
        for (int i = 0; i < x.length; i++) {
            sum += (x[i] - meanX) * (y[i] - meanY);
        }
        return sum / (x.length - 1); // 样本协方差
    }
}

📌 应用:判断“用户活跃度”与“embedding 范数”是否相关。若协方差接近 0,说明范数无信息量,可考虑归一化。


三、实战:从 KES 读取特征指标,计算健康度

假设我们在 KES 中有一张表,记录每日用户 embedding 的统计信息:

CREATE TABLE ai_monitor.embedding_stats (
    dt         DATE PRIMARY KEY,
    user_count INT,
    mean_norm  REAL,
    std_norm   REAL,
    min_norm   REAL,
    max_norm   REAL
);

步骤 1:用 Java 拉取最近 30 天数据

public List<EmbeddingStat> loadRecentStats() {
    String sql = """
        SELECT dt, mean_norm, std_norm 
        FROM ai_monitor.embedding_stats 
        WHERE dt >= CURRENT_DATE - INTERVAL '30 days'
        ORDER BY dt
        """;
    
    List<EmbeddingStat> stats = new ArrayList<>();
    try (Connection conn = KESDataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement(sql);
         ResultSet rs = ps.executeQuery()) {
        
        while (rs.next()) {
            stats.add(new EmbeddingStat(
                rs.getDate("dt").toLocalDate(),
                rs.getDouble("mean_norm"),
                rs.getDouble("std_norm")
            ));
        }
    } catch (SQLException e) {
        throw new RuntimeException("加载统计失败", e);
    }
    return stats;
}

🔗 驱动请从 电科金仓官网下载,确保兼容 REAL 类型。


步骤 2:检测异常波动

public void checkAnomaly() {
    List<EmbeddingStat> stats = loadRecentStats();
    if (stats.size() < 7) return;

    // 提取最近 7 天的标准差
    double[] recentStd = stats.stream()
                              .skip(stats.size() - 7)
                              .mapToDouble(EmbeddingStat::stdNorm)
                              .toArray();

    // 计算历史(前23天)标准差的均值和标准差
    double[] historyStd = stats.stream()
                               .limit(stats.size() - 7)
                               .mapToDouble(EmbeddingStat::stdNorm)
                               .toArray();

    double histMean = UnivariateStats.mean(historyStd);
    double histStdDev = UnivariateStats.stdDev(historyStd);

    // 当前均值(最近7天)
    double currentMean = UnivariateStats.mean(recentStd);

    // Z-Score > 2 视为异常
    double zScore = (currentMean - histMean) / histStdDev;
    if (Math.abs(zScore) > 2.0) {
        alertService.sendAlert(
            "Embedding 范数波动异常!Z-Score = " + String.format("%.2f", zScore)
        );
    }
}

✅ 这就是一个简单的 统计过程控制(SPC) 系统,无需机器学习,仅靠基础统计即可实现。


四、进阶:协方差矩阵与特征相关性分析

在多维特征场景中,我们常需计算协方差矩阵,用于 PCA 降维或特征选择。

// 输入:n 个样本,每个样本 m 维
// 输出:m x m 协方差矩阵
public static double[][] covarianceMatrix(double[][] data) {
    int n = data.length;     // 样本数
    int m = data[0].length;  // 特征数

    // 计算每列均值
    double[] means = new double[m];
    for (int j = 0; j < m; j++) {
        double sum = 0;
        for (int i = 0; i < n; i++) {
            sum += data[i][j];
        }
        means[j] = sum / n;
    }

    // 计算协方差矩阵
    double[][] cov = new double[m][m];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j <= i; j++) {
            double sum = 0;
            for (int k = 0; k < n; k++) {
                sum += (data[k][i] - means[i]) * (data[k][j] - means[j]);
            }
            cov[i][j] = cov[j][i] = sum / (n - 1);
        }
    }
    return cov;
}

💡 应用:若两个特征协方差接近 0,可考虑删除其一,降低模型复杂度。


五、与 KES 协同:用 SQL 预聚合,减少 Java 负载

其实,很多统计量 KES 本身就能高效计算:

-- 直接在数据库计算均值、方差
SELECT 
    AVG(l2_norm) AS mean_norm,
    STDDEV_SAMP(l2_norm) AS std_norm
FROM ai_features.user_embedding;

这样,Java 只需处理聚合结果,而非原始百万级数据。

合理分工:

  • KES:做大规模聚合(AVG, STDDEV, CORR);
  • Java:做逻辑判断、告警、模型集成。

结语:统计思维,是 AI 工程师的第二直觉

AI 不只是“预测未来”,更是“理解现在”。
而理解现在的工具,就是概率与统计。

当你能从 KES 的一行 SQL 或 Java 的一段统计代码中,看出数据的健康与否、特征的有效与否——你就拥有了比模型更底层的洞察力。

下一期,我们会讲:AI数学基础(四):优化理论入门 —— 梯度下降的 Java 实现与收敛分析
敬请期待。

—— 一位相信“不懂统计的 AI 工程师,就像不会看仪表盘的飞行员”的架构师

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐