代码示例 / 快速Keras食谱 / 概率贝叶斯神经网络

概率贝叶斯神经网络

作者: Khalid Salama
创建日期: 2021/01/15
最后修改: 2021/01/15
描述: 使用TensorFlow Probability构建概率贝叶斯神经网络模型。

在Colab中查看 GitHub源代码


介绍

采用概率方法进行深度学习可以考虑不确定性,使得模型能够对错误预测分配较低的置信度等级。不确定性的来源可以在数据中找到,因测量误差或标签中的噪声,或在模型中,由于数据可用性不足,使得模型无法有效学习。

此示例演示如何构建基本的概率贝叶斯神经网络,以应对这两种类型的不确定性。我们使用与Keras API兼容的TensorFlow Probability库。

此示例需要TensorFlow 2.3或更高版本。您可以使用以下命令安装TensorFlow Probability:

pip install tensorflow-probability

数据集

我们使用Wine Quality数据集,该数据集可在TensorFlow Datasets中获得。我们使用红酒子集,其中包含4,898个示例。该数据集具有11个酒的数值物理化学特征,任务是预测酒的质量,分数在0到10之间。在此示例中,我们将此视为回归任务。

您可以使用以下命令安装TensorFlow Datasets:

pip install tensorflow-datasets

设置

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow_datasets as tfds
import tensorflow_probability as tfp

创建训练和评估数据集

在这里,我们使用tfds.load()加载wine_quality数据集,并将目标特征转换为浮点类型。然后,我们打乱数据集并将其拆分为训练集和测试集。我们将前train_size个示例作为训练拆分,其余作为测试拆分。

def get_train_and_test_splits(train_size, batch_size=1):
    # 我们使用与数据集大小相同的缓冲区进行预取,因为数据集
    # 非常小并可以放入内存中。
    dataset = (
        tfds.load(name="wine_quality", as_supervised=True, split="train")
        .map(lambda x, y: (x, tf.cast(y, tf.float32)))
        .prefetch(buffer_size=dataset_size)
        .cache()
    )
    # 我们使用与数据集大小相同的缓冲区进行洗牌。
    train_dataset = (
        dataset.take(train_size).shuffle(buffer_size=train_size).batch(batch_size)
    )
    test_dataset = dataset.skip(train_size).batch(batch_size)

    return train_dataset, test_dataset

编译、训练和评估模型

hidden_units = [8, 8]
learning_rate = 0.001


def run_experiment(model, loss, train_dataset, test_dataset):

    model.compile(
        optimizer=keras.optimizers.RMSprop(learning_rate=learning_rate),
        loss=loss,
        metrics=[keras.metrics.RootMeanSquaredError()],
    )

    print("开始训练模型...")
    model.fit(train_dataset, epochs=num_epochs, validation_data=test_dataset)
    print("模型训练完成。")
    _, rmse = model.evaluate(train_dataset, verbose=0)
    print(f"训练RMSE: {round(rmse, 3)}")

    print("评估模型性能...")
    _, rmse = model.evaluate(test_dataset, verbose=0)
    print(f"测试RMSE: {round(rmse, 3)}")

创建模型输入

FEATURE_NAMES = [
    "固定酸度",
    "挥发性酸度",
    "柠檬酸",
    "残余糖",
    "氯化物",
    "游离二氧化硫",
    "总二氧化硫",
    "密度",
    "pH",
    "硫酸盐",
    "酒精",
]


def create_model_inputs():
    inputs = {}
    for feature_name in FEATURE_NAMES:
        inputs[feature_name] = layers.Input(
            name=feature_name, shape=(1,), dtype=tf.float32
        )
    return inputs

实验1:标准神经网络

我们创建一个标准的确定性神经网络模型作为基线。

def create_baseline_model():
    inputs = create_model_inputs()
    input_values = [value for _, value in sorted(inputs.items())]
    features = keras.layers.concatenate(input_values)
    features = layers.BatchNormalization()(features)

    # 使用Dense层创建具有确定性权重的隐藏层。
    for units in hidden_units:
        features = layers.Dense(units, activation="sigmoid")(features)
    # 输出是确定性的:一个单点估计。
    outputs = layers.Dense(units=1)(features)

    model = keras.Model(inputs=inputs, outputs=outputs)
    return model

让我们将葡萄酒数据集分成训练集和测试集,分别占例子的85%和15%。

dataset_size = 4898
batch_size = 256
train_size = int(dataset_size * 0.85)
train_dataset, test_dataset = get_train_and_test_splits(train_size, batch_size)

现在让我们训练基线模型。我们使用MeanSquaredError作为损失函数。

num_epochs = 100
mse_loss = keras.losses.MeanSquaredError()
baseline_model = create_baseline_model()
run_experiment(baseline_model, mse_loss, train_dataset, test_dataset)
开始训练模型...
Epoch 1/100
17/17 [==============================] - 1s 53ms/step - loss: 37.5710 - root_mean_squared_error: 6.1294 - val_loss: 35.6750 - val_root_mean_squared_error: 5.9729
Epoch 2/100
17/17 [==============================] - 0s 7ms/step - loss: 35.5154 - root_mean_squared_error: 5.9594 - val_loss: 34.2430 - val_root_mean_squared_error: 5.8518
Epoch 3/100
17/17 [==============================] - 0s 7ms/step - loss: 33.9975 - root_mean_squared_error: 5.8307 - val_loss: 32.8003 - val_root_mean_squared_error: 5.7272
Epoch 4/100
17/17 [==============================] - 0s 12ms/step - loss: 32.5928 - root_mean_squared_error: 5.7090 - val_loss: 31.3385 - val_root_mean_squared_error: 5.5981
Epoch 5/100
17/17 [==============================] - 0s 7ms/step - loss: 30.8914 - root_mean_squared_error: 5.5580 - val_loss: 29.8659 - val_root_mean_squared_error: 5.4650

...

Epoch 95/100
17/17 [==============================] - 0s 6ms/step - loss: 0.6927 - root_mean_squared_error: 0.8322 - val_loss: 0.6901 - val_root_mean_squared_error: 0.8307
Epoch 96/100
17/17 [==============================] - 0s 6ms/step - loss: 0.6929 - root_mean_squared_error: 0.8323 - val_loss: 0.6866 - val_root_mean_squared_error: 0.8286
Epoch 97/100
17/17 [==============================] - 0s 6ms/step - loss: 0.6582 - root_mean_squared_error: 0.8112 - val_loss: 0.6797 - val_root_mean_squared_error: 0.8244
Epoch 98/100
17/17 [==============================] - 0s 6ms/step - loss: 0.6733 - root_mean_squared_error: 0.8205 - val_loss: 0.6740 - val_root_mean_squared_error: 0.8210
Epoch 99/100
17/17 [==============================] - 0s 7ms/step - loss: 0.6623 - root_mean_squared_error: 0.8138 - val_loss: 0.6713 - val_root_mean_squared_error: 0.8193
Epoch 100/100
17/17 [==============================] - 0s 6ms/step - loss: 0.6522 - root_mean_squared_error: 0.8075 - val_loss: 0.6666 - val_root_mean_squared_error: 0.8165
模型训练完成。
训练RMSE: 0.809
评估模型性能...
测试RMSE: 0.816

我们从测试集中抽取一个样本,使用模型来获取它们的预测。注意,由于基线模型是确定性的,因此我们为每个测试示例获得一个点估计的预测,没有关于模型或预测不确定性的信息。

sample = 10
examples, targets = list(test_dataset.unbatch().shuffle(batch_size * 10).batch(sample))[
    0
]

predicted = baseline_model(examples).numpy()
for idx in range(sample):
    print(f"预测: {round(float(predicted[idx][0]), 1)} - 实际: {targets[idx]}")
预测: 6.0 - 实际: 6.0
预测: 6.2 - 实际: 6.0
预测: 5.8 - 实际: 7.0
预测: 6.0 - 实际: 5.0
预测: 5.7 - 实际: 5.0
预测: 6.2 - 实际: 7.0
预测: 5.6 - 实际: 5.0
预测: 6.2 - 实际: 6.0
预测: 6.2 - 实际: 6.0
预测: 6.2 - 实际: 7.0

实验 2: 贝叶斯神经网络(BNN)

贝叶斯方法建模神经网络的目标是捕捉认知不确定性,这种不确定性是由于有限的训练数据导致的模型适应性不确定性。

其想法是,与其在神经网络中学习特定的权重(和偏置),不如贝叶斯方法学习权重分布 - 我们可以从中采样以产生给定输入的输出 - 来编码权重的不确定性。

因此,我们需要定义这些权重的先验和后验分布,训练过程是学习这些分布的参数。

# 将先验权重分布定义为均值为0,标准差为1的正态分布。
# 请注意,在此示例中,先验分布不可训练,
# 因为我们固定了其参数。
def prior(kernel_size, bias_size, dtype=None):
    n = kernel_size + bias_size
    prior_model = keras.Sequential(
        [
            tfp.layers.DistributionLambda(
                lambda t: tfp.distributions.MultivariateNormalDiag(
                    loc=tf.zeros(n), scale_diag=tf.ones(n)
                )
            )
        ]
    )
    return prior_model


# 将变分后验权重分布定义为多元高斯分布。
# 请注意,该分布的可学习参数是均值、方差和协方差。
def posterior(kernel_size, bias_size, dtype=None):
    n = kernel_size + bias_size
    posterior_model = keras.Sequential(
        [
            tfp.layers.VariableLayer(
                tfp.layers.MultivariateNormalTriL.params_size(n), dtype=dtype
            ),
            tfp.layers.MultivariateNormalTriL(n),
        ]
    )
    return posterior_model

我们在神经网络模型中使用 tfp.layers.DenseVariational 层,而不是标准的 keras.layers.Dense 层。

def create_bnn_model(train_size):
    inputs = create_model_inputs()
    features = keras.layers.concatenate(list(inputs.values()))
    features = layers.BatchNormalization()(features)

    # 使用 DenseVariational 层创建带有权重不确定性的隐藏层。
    for units in hidden_units:
        features = tfp.layers.DenseVariational(
            units=units,
            make_prior_fn=prior,
            make_posterior_fn=posterior,
            kl_weight=1 / train_size,
            activation="sigmoid",
        )(features)

    # 输出是确定性的:单一的点估计。
    outputs = layers.Dense(units=1)(features)
    model = keras.Model(inputs=inputs, outputs=outputs)
    return model

随着训练数据集大小的增加,认知不确定性可以减少。也就是说,BNN 模型看到的数据越多,它对权重(分布参数)的估计就越确定。我们通过在一个小的训练子集上训练 BNN 模型,然后在完整的训练集上进行训练,以比较输出方差,从而测试这种行为。

使用小训练子集训练 BNN。

num_epochs = 500
train_sample_size = int(train_size * 0.3)
small_train_dataset = train_dataset.unbatch().take(train_sample_size).batch(batch_size)

bnn_model_small = create_bnn_model(train_sample_size)
run_experiment(bnn_model_small, mse_loss, small_train_dataset, test_dataset)
开始训练模型...
训练轮次 1/500
5/5 [==============================] - 2s 123ms/步 - 损失: 34.5497 - 均方根误差: 5.8764 - 验证损失: 37.1164 - 验证均方根误差: 6.0910
训练轮次 2/500
5/5 [==============================] - 0s 28ms/步 - 损失: 36.0738 - 均方根误差: 6.0007 - 验证损失: 31.7373 - 验证均方根误差: 5.6322
训练轮次 3/500
5/5 [==============================] - 0s 29ms/步 - 损失: 33.3177 - 均方根误差: 5.7700 - 验证损失: 36.2135 - 验证均方根误差: 6.0164
训练轮次 4/500
5/5 [==============================] - 0s 30ms/步 - 损失: 35.1247 - 均方根误差: 5.9232 - 验证损失: 35.6158 - 验证均方根误差: 5.9663
训练轮次 5/500
5/5 [==============================] - 0s 23ms/步 - 损失: 34.7653 - 均方根误差: 5.8936 - 验证损失: 34.3038 - 验证均方根误差: 5.8556

...

训练轮次 495/500
5/5 [==============================] - 0s 24ms/步 - 损失: 0.6978 - 均方根误差: 0.8162 - 验证损失: 0.6258 - 验证均方根误差: 0.7723
训练轮次 496/500
5/5 [==============================] - 0s 22ms/步 - 损失: 0.6448 - 均方根误差: 0.7858 - 验证损失: 0.6372 - 验证均方根误差: 0.7808
训练轮次 497/500
5/5 [==============================] - 0s 23ms/步 - 损失: 0.6871 - 均方根误差: 0.8121 - 验证损失: 0.6437 - 验证均方根误差: 0.7825
训练轮次 498/500
5/5 [==============================] - 0s 23ms/步 - 损失: 0.6213 - 均方根误差: 0.7690 - 验证损失: 0.6581 - 验证均方根误差: 0.7922
训练轮次 499/500
5/5 [==============================] - 0s 22ms/步 - 损失: 0.6604 - 均方根误差: 0.7913 - 验证损失: 0.6522 - 验证均方根误差: 0.7908
训练轮次 500/500
5/5 [==============================] - 0s 22ms/步 - 损失: 0.6190 - 均方根误差: 0.7678 - 验证损失: 0.6734 - 验证均方根误差: 0.8037
模型训练完成。
训练均方根误差: 0.805
评估模型性能...
测试均方根误差: 0.801

由于我们已经训练了一个 BNN 模型,模型每次在相同输入下调用时都会产生不同的输出,因为每次都会从分布中抽样一组新的权重来构建网络并生成输出。模式权重的不确定性越小,我们将看到的相同输入的输出的可变性(范围越宽)就越大。

def compute_predictions(model, iterations=100):
    predicted = []
    for _ in range(iterations):
        predicted.append(model(examples).numpy())
    predicted = np.concatenate(predicted, axis=1)

    prediction_mean = np.mean(predicted, axis=1).tolist()
    prediction_min = np.min(predicted, axis=1).tolist()
    prediction_max = np.max(predicted, axis=1).tolist()
    prediction_range = (np.max(predicted, axis=1) - np.min(predicted, axis=1)).tolist()

    for idx in range(sample):
        print(
            f"预测均值: {round(prediction_mean[idx], 2)}, "
            f"最小值: {round(prediction_min[idx], 2)}, "
            f"最大值: {round(prediction_max[idx], 2)}, "
            f"范围: {round(prediction_range[idx], 2)} - "
            f"实际值: {targets[idx]}"
        )


compute_predictions(bnn_model_small)
预测均值:5.63,最小值:4.92,最大值:6.15,范围:1.23 - 实际:6.0 预测均值:6.35,最小值:6.01,最大值:6.54,范围:0.53 - 实际:6.0 预测均值:5.65,最小值:4.84,最大值:6.25,范围:1.41 - 实际:7.0 预测均值:5.74,最小值:5.21,最大值:6.25,范围:1.04 - 实际:5.0 预测均值:5.99,最小值:5.26,最大值:6.29,范围:1.03 - 实际:5.0 预测均值:6.26,最小值:6.01,最大值:6.47,范围:0.46 - 实际:7.0 预测均值:5.28,最小值:4.73,最大值:5.86,范围:1.12 - 实际:5.0 预测均值:6.34,最小值:6.06,最大值:6.53,范围:0.47 - 实际:6.0 预测均值:6.23,最小值:5.91,最大值:6.44,范围:0.53 - 实际:6.0 预测均值:6.33,最小值:6.05,最大值:6.54,范围:0.48 - 实际:7.0

用完整的训练集训练BNN。

num_epochs = 500
bnn_model_full = create_bnn_model(train_size)
run_experiment(bnn_model_full, mse_loss, train_dataset, test_dataset)

compute_predictions(bnn_model_full)
开始训练模型...
Epoch 1/500
17/17 [==============================] - 2s 32ms/step - loss: 25.4811 - root_mean_squared_error: 5.0465 - val_loss: 23.8428 - val_root_mean_squared_error: 4.8824
Epoch 2/500
17/17 [==============================] - 0s 7ms/step - loss: 23.0849 - root_mean_squared_error: 4.8040 - val_loss: 24.1269 - val_root_mean_squared_error: 4.9115
Epoch 3/500
17/17 [==============================] - 0s 7ms/step - loss: 22.5191 - root_mean_squared_error: 4.7449 - val_loss: 23.3312 - val_root_mean_squared_error: 4.8297
Epoch 4/500
17/17 [==============================] - 0s 7ms/step - loss: 22.9571 - root_mean_squared_error: 4.7896 - val_loss: 24.4072 - val_root_mean_squared_error: 4.9399
Epoch 5/500
17/17 [==============================] - 0s 6ms/step - loss: 21.4049 - root_mean_squared_error: 4.6245 - val_loss: 21.1895 - val_root_mean_squared_error: 4.6027

...

Epoch 495/500
17/17 [==============================] - 0s 7ms/step - loss: 0.5799 - root_mean_squared_error: 0.7511 - val_loss: 0.5902 - val_root_mean_squared_error: 0.7572
Epoch 496/500
17/17 [==============================] - 0s 6ms/step - loss: 0.5926 - root_mean_squared_error: 0.7603 - val_loss: 0.5961 - val_root_mean_squared_error: 0.7616
Epoch 497/500
17/17 [==============================] - 0s 7ms/step - loss: 0.5928 - root_mean_squared_error: 0.7595 - val_loss: 0.5916 - val_root_mean_squared_error: 0.7595
Epoch 498/500
17/17 [==============================] - 0s 7ms/step - loss: 0.6115 - root_mean_squared_error: 0.7715 - val_loss: 0.5869 - val_root_mean_squared_error: 0.7558
Epoch 499/500
17/17 [==============================] - 0s 7ms/step - loss: 0.6044 - root_mean_squared_error: 0.7673 - val_loss: 0.6007 - val_root_mean_squared_error: 0.7645
Epoch 500/500
17/17 [==============================] - 0s 7ms/step - loss: 0.5853 - root_mean_squared_error: 0.7550 - val_loss: 0.5999 - val_root_mean_squared_error: 0.7651
模型训练完成。
训练RMSE: 0.762
评估模型性能...
测试RMSE: 0.759
预测均值: 5.41, 最小值: 5.06, 最大值: 5.9, 范围: 0.84 - 实际: 6.0
预测均值: 6.5, 最小值: 6.16, 最大值: 6.61, 范围: 0.44 - 实际: 6.0
预测均值: 5.59, 最小值: 4.96, 最大值: 6.0, 范围: 1.04 - 实际: 7.0
预测均值: 5.67, 最小值: 5.25, 最大值: 6.01, 范围: 0.76 - 实际: 5.0
预测均值: 6.02, 最小值: 5.68, 最大值: 6.39, 范围: 0.71 - 实际: 5.0
预测均值: 6.35, 最小值: 6.11, 最大值: 6.52, 范围: 0.41 - 实际: 7.0
预测均值: 5.21, 最小值: 4.85, 最大值: 5.68, 范围: 0.83 - 实际: 5.0
预测均值: 6.53, 最小值: 6.35, 最大值: 6.64, 范围: 0.28 - 实际: 6.0
预测均值: 6.3, 最小值: 6.05, 最大值: 6.47, 范围: 0.42 - 实际: 6.0
预测均值: 6.44, 最小值: 6.19, 最大值: 6.59, 范围: 0.4 - 实际: 7.0

注意,使用完整训练数据集训练的模型在相同输入的预测值范围(不确定性)较小, 与使用训练数据集的子集训练的模型相比。


实验3: 概率贝叶斯神经网络

到目前为止,我们构建的标准和贝叶斯NN模型的输出是 确定性的,即为给定示例生成一个点估计作为预测。 我们可以通过让模型输出一个分布来创建一个概率神经网络。 在这种情况下,模型还捕捉到不确定性, 这是由于数据中的不可减少噪声,或生成数据的过程的随机特性。

在此示例中,我们将输出建模为IndependentNormal分布, 具有可学习的均值和方差参数。如果任务是分类, 我们将使用IndependentBernoulli与二元类,以及OneHotCategorical 与多个类,以建模模型输出的分布。

def create_probablistic_bnn_model(train_size):
    inputs = create_model_inputs()
    features = keras.layers.concatenate(list(inputs.values()))
    features = layers.BatchNormalization()(features)

    # 使用DenseVariational层创建具有权重不确定性的隐藏层。
    for units in hidden_units:
        features = tfp.layers.DenseVariational(
            units=units,
            make_prior_fn=prior,
            make_posterior_fn=posterior,
            kl_weight=1 / train_size,
            activation="sigmoid",
        )(features)

    # 创建一个概率输出(正态分布),并使用`Dense`层
    # 来生成分布的参数。
    # 我们设置units=2以学习正态分布的均值和方差。
    distribution_params = layers.Dense(units=2)(features)
    outputs = tfp.layers.IndependentNormal(1)(distribution_params)

    model = keras.Model(inputs=inputs, outputs=outputs)
    return model

由于模型的输出是一个分布,而不是一个点估计,我们使用负对数似然作为我们的损失函数,以计算从模型产生的估计分布中看到真实数据(目标)的可能性。

def negative_loglikelihood(targets, estimated_distribution):
    return -estimated_distribution.log_prob(targets)


num_epochs = 1000
prob_bnn_model = create_probablistic_bnn_model(train_size)
run_experiment(prob_bnn_model, negative_loglikelihood, train_dataset, test_dataset)
开始训练模型...
Epoch 1/1000
17/17 [==============================] - 2s 36ms/step - loss: 11.2378 - root_mean_squared_error: 6.6758 - val_loss: 8.5554 - val_root_mean_squared_error: 6.6240
Epoch 2/1000
17/17 [==============================] - 0s 7ms/step - loss: 11.8285 - root_mean_squared_error: 6.5718 - val_loss: 8.2138 - val_root_mean_squared_error: 6.5256
Epoch 3/1000
17/17 [==============================] - 0s 7ms/step - loss: 8.8566 - root_mean_squared_error: 6.5369 - val_loss: 5.8749 - val_root_mean_squared_error: 6.3394
Epoch 4/1000
17/17 [==============================] - 0s 7ms/step - loss: 7.8191 - root_mean_squared_error: 6.3981 - val_loss: 7.6224 - val_root_mean_squared_error: 6.4473
Epoch 5/1000
17/17 [==============================] - 0s 7ms/step - loss: 6.2598 - root_mean_squared_error: 6.4613 - val_loss: 5.9415 - val_root_mean_squared_error: 6.3466

...

Epoch 995/1000
17/17 [==============================] - 0s 7ms/step - loss: 1.1323 - root_mean_squared_error: 1.0431 - val_loss: 1.1553 - val_root_mean_squared_error: 1.1060
Epoch 996/1000
17/17 [==============================] - 0s 7ms/step - loss: 1.1613 - root_mean_squared_error: 1.0686 - val_loss: 1.1554 - val_root_mean_squared_error: 1.0370
Epoch 997/1000
17/17 [==============================] - 0s 7ms/step - loss: 1.1351 - root_mean_squared_error: 1.0628 - val_loss: 1.1472 - val_root_mean_squared_error: 1.0813
Epoch 998/1000
17/17 [==============================] - 0s 7ms/step - loss: 1.1324 - root_mean_squared_error: 1.0858 - val_loss: 1.1527 - val_root_mean_squared_error: 1.0578
Epoch 999/1000
17/17 [==============================] - 0s 7ms/step - loss: 1.1591 - root_mean_squared_error: 1.0801 - val_loss: 1.1483 - val_root_mean_squared_error: 1.0442
Epoch 1000/1000
17/17 [==============================] - 0s 7ms/step - loss: 1.1402 - root_mean_squared_error: 1.0554 - val_loss: 1.1495 - val_root_mean_squared_error: 1.0389
模型训练完成。
训练 RMSE: 1.068
评估模型性能...
测试 RMSE: 1.068

现在让我们从模型中生成给定测试样本的输出。输出现在是一个分布,我们可以使用它的均值和方差来计算预测的置信区间(CI)。

prediction_distribution = prob_bnn_model(examples)
prediction_mean = prediction_distribution.mean().numpy().tolist()
prediction_stdv = prediction_distribution.stddev().numpy()

# 95% CI 计算为 mean ± (1.96 * stdv)
upper = (prediction_mean + (1.96 * prediction_stdv)).tolist()
lower = (prediction_mean - (1.96 * prediction_stdv)).tolist()
prediction_stdv = prediction_stdv.tolist()

for idx in range(sample):
    print(
        f"预测均值: {round(prediction_mean[idx][0], 2)}, "
        f"标准差: {round(prediction_stdv[idx][0], 2)}, "
        f"95% CI: [{round(upper[idx][0], 2)} - {round(lower[idx][0], 2)}]"
        f" - 实际: {targets[idx]}"
    )
预测均值: 5.29, 标准差: 0.66, 95% CI: [6.58 - 4.0] - 实际: 6.0
预测均值: 6.49, 标准差: 0.81, 95% CI: [8.08 - 4.89] - 实际: 6.0
预测均值: 5.85, 标准差: 0.7, 95% CI: [7.22 - 4.48] - 实际: 7.0
预测均值: 5.59, 标准差: 0.69, 95% CI: [6.95 - 4.24] - 实际: 5.0
预测均值: 6.37, 标准差: 0.87, 95% CI: [8.07 - 4.67] - 实际: 5.0
预测均值: 6.34, 标准差: 0.78, 95% CI: [7.87 - 4.81] - 实际: 7.0
预测均值: 5.14, 标准差: 0.65, 95% CI: [6.4 - 3.87] - 实际: 5.0
预测均值: 6.49, 标准差: 0.81, 95% CI: [8.09 - 4.89] - 实际: 6.0
预测均值: 6.25, 标准差: 0.77, 95% CI: [7.76 - 4.74] - 实际: 6.0
预测均值: 6.39, 标准差: 0.78, 95% CI: [7.92 - 4.85] - 实际: 7.0