# 2.4 如何解释模型
现在我们已经训练了模型,并且能够做出合理的预测,很自然地想知道它学到了什么。有什么方法可以窥视模型以了解其如何理解数据?当模型预测某种物品的特定价格时,您是否有可能找到一个可理解的解释,说明其为何会产生该价格?对于大型深度网络的一般情况,模型理解(也称为模型可解释性)仍然是活跃的研究领域,在学术会议上有着很多讨论和演讲。但是对于这个简单的线性回归模型来说,它非常简单。
到本节末,您将
- 能够从模型中提取权重
- 能够解释这些权重
# 2.4.1 从学习中提取权重
我们在 2.3 节中建立的简单线性模型包含 13 个学习的参数,就像 2.1.3 节中的第一个线性模型包含 kernel 和 bias 一样
output = kernel · features + bias
kernel 和 bias 都是在拟合模型时学习的。与第 2.1.3 节中学习的标量线性函数相反,此处的特征和 kernel 都是矢量,而“ · ”号表示内积,即标量乘以矢量的一般化。内积,也称为“点积” ,仅是匹配元素的乘积之和。下面清单 2.16 中的伪代码更精确地定义了内部乘积。
因此,我们应该认为,特征元素与 kernel 元素之间存在关联。如表 2.1 所示,对于每个单独的特征元素,例如“犯罪率”和“一氧化氮浓度”,kernel 中都有一个相关的数值。每个值都告诉我们有关模型从该特征中学到了什么以及该特征如何影响输出的信息。
# 清单 2.16 内部产品伪代码
function innerProduct(a, b) {
output = 0;
for (let i = 0; i < a.length; i++) {
output += a[i] * b[i];
}
return output;
}
例如,如果模型得知 kernel [i] 为正,则意味着 feature [i] 值越大,输出就越大。反之亦然,如果模型得知 kernel [j] 为负,则 feature [j] 值的较大值会降低预测的输出。值很小的学习值表示模型认为关联的特征对预测影响很小,而值很大的学习值表示模型将重点放在特征上,此特征变化很小,但会对预测产生较大影响。[48]
更具体的说,在图 2.13 中,以绝对值排在波士顿房屋示例的输出区域中的前五个特征值被打印出来。我们可以看到,对房地产价格产生负面影响的功能,这些值是负值,例如本地居民辍学的速度以及房地产与理想工作地点之间的距离。学习得到的权重对于得到与我们期望价格有着直接相关的特征(例如酒店的房间数量)具有积极意义。
# 图 2.13 按绝对值排序,它们是在线性模型中对波士顿住房预测问题学习到的前五名权重的特征。请注意,您希望对房屋价格产生负面影响的要素呈负值。
|辍学率 |-3.8119| |通勤距离| -3.7278| |每间房屋的房间数| 2.8451| |距公路距离| 2.2949| |一氧化氮浓度| -2.1190|
# 2.4.2 从模型中提取权重
学习模型的模块化结构使提取相关系数变得容易,我们可以直接访问它们,但是要获取原始值,需要达到一些 API 级别。重要的是,由于该值可能在 GPU 上,并且通信非常昂贵,因此请求这些值是异步的。下面清单 2.17 中的代码是对 model.fit 回调的补充,扩展了清单 2.14 来说明每次学习到的系数。我们将逐步介绍 API 调用。
给定模型,我们首先希望访问正确的层。这很容易,因为此模型中只有一层,因此我们可以在 model.layers [0] 处得到它的句柄。现在我们有了图层,我们可以使用 getWeights()访问内部权重,会返回一个权重数组。对于密集层,它将始终包含两个系数,即 kernel 系数和 bias 系数。因此,我们可以在以下位置访问正确的张量 。
> model.layers[0].getWeights()[0]
现在我们有了正确的 Tensor ,我们可以通过调用 tensor 的 data()方法来访问其内容。由于 GPU 的异步性质 ↔ CPU 通信,data()是一个异步并返回一个 promise 张量的值,而不是实际的数值。在下面的代码清单中,传递给 promise 的 then()方法的回调将张量值绑定到一个名为 kernelAsArr 的变量。如果未注释 console.log()语句,则每训练一次将如下所示的语句(列出 kernel 的值)记录到控制台。
# 清单 2.17 访问内部模型值
let trainLoss;
let valLoss;
await model.fit(tensors.trainFeatures, tensors.trainTarget, {
batchSize: BATCH_SIZE,
epochs: NUM_EPOCHS,
validationSplit: 0.2,
callbacks: {
onEpochEnd: async (epoch, logs) => {
await ui.updateStatus(`Epoch ${epoch + 1} of ${NUM_EPOCHS} completed.`);
trainLoss = logs.loss;
valLoss = logs.val_loss;
await ui.plotData(epoch, trainLoss, valLoss);
model.layers[0].getWeights()[0].data().then(kernelAsArr => {
#Console.log(kernelAsArr);
const weightsList = describeKerenelElements(kernelAsArr);
ui.updateWeightDescription(weightsList);
});
}
}
});
# 2.4.3 关于可解释性
您可能会看图 2.13 所描述的结果,并承认该模型已得到“每间房屋的房间数”特征与价格输出呈正相关,或者房地产“AGE”特征(未列出)相对于前五个功能而言,其绝对值的重要性较低。但是这种分析可能会失败,原因之一即是两个输入特征之间具有很强的相关性。 考虑一个假设的示例,其中同一特征被两次输入,称它们为 FEAT1 和 FEAT2。想象一下,从这两个特征中学到的权重是 10 和-5。您可能倾向于说增加 FEAT1 会导致更大的输出,而 FEAT2 则相反。但是,由于特征是等效的,即使权重反转,模型也将输出完全相同的值。
需要注意的是相关性和因果关系之间的差异。想象一个简单的模型,我们希望通过屋顶的潮湿程度来预测外面下雨的程度。如果我们测量了屋顶的湿度,我们可能可以预测过去一个小时的降雨量。但是,我们不能将水溅到传感器来模拟这个问题!