你的模型正确吗?
作者 | Vini Jaiswal
译者 | 张健欣
策划 | 刘燕 使用 Tensorflow Probability 来量化不确定性。在本文中,我们将关注机器学习和深度学习的不确定性量化问题。这不是一个新课题,但 Tensorflow Probability 和 Pyro 等工具的引入,使得量化建模来更高效地计算不确定性变得容易。考虑这样一个场景:我们根据一系列特征值来预测房屋等资产的价值,来驱动购买决策。知道我们对于这些预测的价格有多少把握,是不是很有好处?Tensorflow Probability 允许你使用熟悉的 Tensorflow 语法和方法,而且增加了使用分布的能力。在本文中,我们将抛开优先级和贝叶斯处理,选择一种更简单的概率处理来说明基本原理。我们使用似然原理(likelihood principle)来说明,如何通过将似然原理应用到深度学习回归问题来进行不确定性度量和获取预测值。
不确定性量化
不确定性可以分为两种类型:
认知不确定性
偶然不确定性
认知不确定性是模型没有信息的结果,但是这些信息可以通过其它方式来获取,例如向模型提供新数据,或者通过增加模型的复杂度来增强模型的表示能力。这种不确定性可以得到解决和减少。另一方面,偶然不确定性源于数据生成过程中固有的随机性。在随机过程中,有许多参数,而只有一部分参数是可观测的。因此,理论上,如果有一种测量所有这些参数的方法,我们就能准确地复现一个事件。然而,在大多数现实场景中,并不存在这样的方法。在本文中,我们尝试量化认知不确定性,这种不确定性源于我们的网络或模型参数中缺乏一些信息。
问题定义
这里的目标是量化预测的不确定性。换句话说,在获得预测值的同时,还会计算每个预测值的不确定性或置信度。我们将使用一个回归问题来说明这种不确定性分析。这里,我们使用神经网络对自变量和因变量之间的关系进行建模。神经网络不再输出单个预测值 y_pred,而是预测一个分布的参数。这种概率分布是根据目标或因变量的类型选择的。对于分类,MaxLike 原理告诉我们,网络权重会被更新,来最大化看到给定模型(网络 + 权重)的真实数据类的可能性或概率。正态分布是基线;然而,它可能并不适用于所有情况。例如,如果目标变量代表计数数据,我们将选择泊松分布。对于正态分布,神经网络将为每个输入数据点输出两个值,即分布参数 y_mean 和 y_std。我们假设输出或目标变量中存在参数分布,这可能有效,也可能无效。对于更复杂的建模,你可能需要考虑高斯混合或混合密度网络。
通常,预测值的误差是使用一些损失函数(例如 MSE、交叉熵等)来计算的。由于我们有概率输出,MSE 不是测量误差的合适方法。相反,我们选择似然函数,甚至是负对数似然(Negative Log-likelihood,NLL)作为基线损失函数。事实上,除了确定性和概率性在本质上的解释差异,还可以看到交叉熵和 NLL 是等价的。为了说明这一点,下面的图 1 中绘制了两个正态分布,虚线表示两个不同数据点处概率密度的可能性。较窄的分布用红色展示,而较宽的分布用蓝色展示。对于较窄的分布,x=68 处的数据点的可能性更高,而对于较宽的分布,x=85 处的数据点的可能性更高。
图 1:两个不同点处的可能性
使用 MaxLike 原理并在假设数据点独立的情况下,这里的目标是最大化每个数据点的可能性。基于独立性假设,总可能性是个体可能性的乘积。对于数值稳定性,我们使用 log-likelihood,而不是 likelihood。将每个点的 NLL 相加,来获得每次迭代的总损失。
我们想要捕捉自变量和因变量之间可能存在的非线性关系,因此我们对参数 y_mean 和 y_std 使用了多个带有激活函数的隐藏层。这些参数可以用非单调的变体。我们可以用两种方式简化这一点:
固定方差:只预估 y_mean 这一个参数
线性方差:也预估 y_std,但现在这是一个单一隐藏层的函数,而且没有激活函数
下面的例子显示了标准差的非线性方差。第一个例子说明了如何拟合线性模型(y_mean 的线性变化),然后是 y_mean 的非线性变化,从而捕捉更复杂的现象。
什么是 Tensorflow Probability (TFP)?
Tensorflow Probability 是一个基于 TensorFlow 的框架,但可以处理数据分布并对其执行操作。你可以定义分布并从中取样,如下文所示。
有哪些分布?
常见的分布,例如伯努利分布、二项分布、正态分布、伽马分布等等。关于这些分布的更多信息可以在这里查看。 [https://www.tensorflow.org/probability/api_docs/python/tfp/distributions]
在合成数据上使用 Tensorflow Probability 的不确定性预测
为了说明如何使用 TFP 来量化预测的不确定性,我们从一个合成的一维数据集开始。合成数据能够使我们进行受控实验,而单一维度使我们很容易将与每个数据点和预测相关的不确定性可视化。
合成数据生成 这里的目标是生成一些具有非恒定方差的合成数据。数据的这种特性称为异方差。这些数据以段的形式生成,然后连接在一起,如下文所示。
使用非常数的标准差来拟合一个线性模型 将一些噪声添加到上述数据中,我们从自变量'x'和噪声中生成目标变量'y'。它们之间的关系是:y=2.7*x+noise
然后将这些数据分为培训集和验证集,以评估性能。图 2 显示了训练集和验证集的因变量和自变量之间的关系。
np.random.seed(4710)noise=np.random.normal(0,x,len(x))np.random.seed(99)first_part=len(x1)x11=np.random.uniform(-1,1,first_part)np.random.seed(97)x12=np.random.uniform(1,6,len(noise)-first_part)x=np.concatenate([x11,x12])x=np.sort(x)y=2.7*x+noise图 2——生成的数据
定义模型 我们建立的模型相当简单,有三个密集层应用于数据和两个输出,对应于平均 y_mean 值和标准差 y_std。这些参数被传给分布函数'my_dist'。
在函数'my_dist'中,正态分布由平均值和标准差参数化。平均值是二维变量'params'中的第一个指标,标准差是通过一个 softplus 操作定义的,因为我们正在计算标准差的对数或 y_std。这是因为标准差始终是正值,神经网络层的输出是正值或负值。因此,转换有助于将输出限定为正值。
顾名思义,'NLL'函数计算在给定网络参数下的输入数据的 Negative Log-likelihood,并返回它们。这就是损失函数。
生成三个模型:
模型——输出 y_mean 和输出分布的 y_std
平均值模型——输出从'my_dist'返回的分布的平均值
标准差模型——输出从'my_dist'返回的分布的标准差
def NLL(y, distr): return -distr.log_prob(y)def my_dist(params): return tfd.Normal(loc=params[:,0:1], scale=1e-3 + tf.math.softplus(0.05 * params[:,1:2]))# both parameters are learnableinputs = Input(shape=(1,))hiddena = Dense(30)(inputs)hidden1 = Dense(20,activation="relu")(hiddena)hidden2 = Dense(20,activation="relu")(hidden1)out1 = Dense(1)(hiddena) #Aout2 = Dense(1)(hidden2) #Bparams = Concatenate()([out1,out2]) #Cdist = tfp.layers.DistributionLambda(my_dist)(params)model_flex_sd = Model(inputs=inputs, outputs=dist)model_flex_sd.compile(Adam(learning_rate=0.01), loss=NLL)
评估结果 一旦对模型进行了训练并检查了收敛图,我们还可以观察测试数据的 NLL 总和。我们会在下一节中更详细地了解这一点。这可用于调整模型和评估拟合,但要注意不要在不同的数据集之间进行比较。NLL 的总和可按如下所示进行计算。
model_flex_sd.evaluate(x_test,y_test, verbose=0)4.0097329248257765
与培训和验证数据拟合的模型如下所示。训练的结果是拟合为一个线性模型,从 y_mean 获取的黑线描述了这一趋势。方差由红色虚线表示,与生成的数据的方差一致。最后,对测试数据集进行评估。
图 3 测试数据的预测平均值和标准差
拟合具有非常数标准差的非线性模型 这里,由于平方项,因变量与自变量之间的关系以非线性方式变化,如下所示。
y = 2.7x + x^2 + noise
为了获得这种非线性行为,我们向 y_mean 的输出中添加了一个激活函数(非线性)。与之前所做的类似,我们拟合模型,并绘制每个数据点的预测平均值和标准差,用于训练、验证和测试数据点,如下所示。
inputs = Input(shape=(1,))hiddena = Dense(30, activation="relu")(inputs)hidden1 = Dense(20,activation="relu")(hiddena)hidden2 = Dense(20,activation="relu")(hidden1)out1 = Dense(1)(hiddena) #Aout2 = Dense(1)(hidden2) #Bparams = Concatenate()([out1,out2]) #Cdist = tfp.layers.DistributionLambda(my_dist)(params)
图 4 非线性生成数据——训练和验证图 5 基于训练和验证数据的预测平均值和标准差图 6 测试数据的预测平均值和标准差
与之前生成的数据不同,现实中往往没有理想的特征,例如单位标准差;因此,预处理数据通常是一个好主意。这对于假设数据分布正态性以使数据有效的技术尤其重要。这里使用的数据集是 Diabetes 数据集。这是一个具有数值特征和目标的回归问题。
数据预处理 这里有两种用来转换数据的预处理方式:
标准化
幂次变换或分位数变换
幂次转换包括 Box-Cox 转换(假设所有值都为正)或 Yeo-Johnson 转换(对数据的性质没有假设)。在分位数转换器中,这两种方法都将数据转换为更像高斯分布的分布。每个特征的分位数信息用于将其映射到所需的分布,即正态分布。
def preprocess_pipeline_power(df, target_column): scaler = StandardScaler() power_transform = PowerTransformer(method='yeo-johnson') pipeline_power = Pipeline([('s', scaler), ('p', power_transform)]) res_power = pipeline_power.fit_transform(df) x_train, x_test, y_train, y_test = train_test_split(res_power[:,0:-1], res_power[:,-1], test_size = 0.2, random_state=123) return(x_train, x_test, y_train, y_test)
def preprocess_pipeline_quantile(df, target_column): scaler = StandardScaler() quantile_transform = QuantileTransformer(n_quantiles=100, output_distribution="normal") pipeline_quantile = Pipeline([('s', scaler), ('q', quantile_transform)]) res_quantile = pipeline_quantile.fit_transform(df) x_train, x_test, y_train, y_test = train_test_split(res_quantile[:,0:-1], res_quantile[:,-1], test_size = 0.2, random_state=123) return(x_train, x_test, y_train, y_test)
模型拟合与评估
def NLL(y, distr): return -distr.log_prob(y)
def my_dist(params): return tfd.Normal(loc=params[:,0:1], scale=1e-3 + tf.math.softplus(0.05 * params[:,1:2]))# both parameters are learnable
def get_model(X): if(isinstance(X, pd.DataFrame)): Xlen = len(X.columns) else: Xlen = np.shape(X)[1] input1 = Input(shape=(Xlen)) # 13 for boston housing and 8 for california housing data hidden1 = Dense(32, activation='relu', name='dense_1')(input1) # 32 or 8 hidden2 = Dense(8, activation='relu', name='dense_2')(input1) out1 = Dense(1, activation='relu', name='out_1')(hidden2) # out1 is mean out2 = Dense(1, activation='relu', name='out_2')(hidden1) # out2 is std params = Concatenate()([out1,out2]) #C dist = tfp.layers.DistributionLambda(my_dist)(params) model = Model(inputs=input1, outputs=dist) model.compile(Adam(learning_rate=0.001), loss=NLL) model_mean = Model(inputs=input1, outputs=dist.mean()) model_std = Model(inputs=input1, outputs=dist.stddev()) model.summary() return(model, model_mean, model_std)
def fit_model(model, X_data_train, y_data_train, batch_size=128, epochs=1000, validation_split=0.1): history = model.fit(X_data_train, y_data_train, batch_size=batch_size, epochs=epochs, validation_split=validation_split) return(model)
def evaluate_model(model, model_mean, model_std, X_data_test, y_data_test): y_out_mean = model_mean.predict(X_data_test) y_out_std = model_std.predict(X_data_test) y_out_mean_vals = y_out_mean.squeeze(axis=1) if(isinstance(y_data_test, pd.DataFrame)): y_test_true_vals = y_data_test.values.squeeze(axis=1) else: y_test_true_vals = y_data_test y_out_std_vals = y_out_std.squeeze(axis=1) neg_log_prob_array = [] for elem in zip(y_out_mean_vals, y_test_true_vals, y_out_std_vals): predicted = elem[0] predicted_var = elem[2] true_val = elem[1] neg_log_prob = -1.0 * tfd.Normal(predicted, predicted_var).log_prob(true_val).numpy() neg_log_prob_array.append(neg_log_prob) return(neg_log_prob_array)
对结果进行评估 如前所述,除了收敛图之外,您还可以使用 NLL 之和基于测试集的性能评估模型的不确定性。该指标为我们提供了一种比较不同模型的方法。我们还可以查看在测试数据集上获得的 NLL 分布,以了解模型如何推广到新的数据点。离群值可能会导致一个大型 NLL,从分布情况来看,这是显而易见的。
model, model_mean, model_std = get_model(X_trans)model = fit_model(model, X_data_train, y_data_train, epochs=1000)neg_log_array = evaluate_model(model, model_mean, model_std, X_data_test, y_data_test)
这里,每个点的 NLL 累积在数组'neg_log_array'中,并绘制直方图。我们比较了两种情况:一种是将分位数转换应用于目标,另一种是将幂次转换版本应用于其它数据。我们希望直方图中的大多数密度接近 0,这表明大多数数据点的 NLL 比较低,即模型很好地拟合这些点。图 7 说明了这两种转换技术,如果您的目标是最小化模型预测不确定性中的异常值,则分位数转换具有更好的性能。这也可以用来执行模型的超参数调整和选择最佳模型。
图 7 NLL 直方图,理想情况下,我们希望在 0 附近的密度更高
结论
这篇文章展示了不确定性量化如何有利于预测建模。此外,我们还使用 Tensorflow Probability 从概率的角度对深度学习问题的不确定性进行了量化。这种方法避免了完全的贝叶斯处理,并且更适合引入到不确定性评估中。
请在我们的机器学习运行时上的 Databricks 上尝试这里展示的例子!
原文链接:
https://databricks.com/blog/2022/04/28/how-wrong-is-your-model.html
你也「在看」吗???
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线