4种调试深度神经网络的方法

https://www.toutiao.com/a6645209884650373639/


深度学习通常被视为一个 Blackbox 。但是 Blackbox 视图对于机器学习从业者来说是一个明显的问题: 如何调试模型?

在本文中,我将介绍我们在心电图中使用的一些技术来调试DeepHeart,这是一个深度神经网络,使用来自Apple Watch,Garmin和WearOS设备的数据来预测疾病。

在心电图上,我们认为构建DNN架构是工程学。

4种调试深度神经网络的方法

心电图很能说明你的一切问题。DeepHeart使用来自Apple Watch、Garmin和Wear

一:预测合成输出

通过预测从输入数据构建的合成输出任务来测试模型的能力。

我们在构建睡眠呼吸暂停检测模型时应用了这一技术。现有的关于睡眠呼吸暂停筛查的文献使用白天和夜间心率标准差的差异作为筛查机制。因此,我们为每周的输入数据创建了一个合成输出任务:

std(白天HRs) - std(夜间HRs)

为了学习这个函数,模型需要能够完成:

  1. 区分白天和黑夜
  2. 记住过去几天的数据

这两个都是预测睡眠呼吸暂停的先决条件,因此我们尝试新架构的第一步是检查它是否能够学习这个合成任务。

您还可以通过在合成任务上预先训练网络,以半监督训练的形式使用此类综合任务。这种方法最适合的是当有很多没有标签的数据时,这个方法是很有用的。

二、可视化激活

很难理解训练模型的内部结构。你如何理解成千上万的矩阵乘法?

我们检查了网络的几个层的激活情况,希望找到一些语义属性,例如在用户睡觉、锻炼或焦虑时细胞的激活情况。

在Keras中,从模型中提取激活的代码很简单。下面的代码片段创建了一个Keras函数last_output_fn,该函数在给定一些输入数据的情况下获得一个层的输出(激活细胞)。

from keras import backend as K
def extract_layer_output(model, layer_name, input_data):
 layer_output_fn = K.function([model.layers[0].input],
 [model.get_layer(layer_name).output])
 layer_output = layer_output_fn([input_data])
 # layer_output.shape is (num_units, num_timesteps)
 return layer_output[0]
4种调试深度神经网络的方法

我们可视化网络的几个激活层。当我们检查第二层卷积层(一个128宽的时间卷积层)的激活时,我们注意到一些奇怪的事情:

4种调试深度神经网络的方法

在每个时间步上激活卷积层的每个单元。蓝色阴影表示激活值。

激活不会随时间而改变!它们不受输入值的影响,被称为“dead neurons”。

4种调试深度神经网络的方法

ReLU激活函数,f(x)= max(0,x)

该架构使用ReLU激活函数,当输入为负时输出零。

在训练的某一时刻,较大的梯度使得一个层的所有偏差项都变得非常负,使得ReLU函数的输入非常负。因此,这一层发出了所有的零,并且由于ReLU的梯度在输入小于零的情况下为零,因此不能通过梯度下降来解决这个问题。

当一个卷积层发出所有0时,后续层中的单元格输出它们的偏差项的值。这就是为什么这一层的每个单元输出不同的值——它们的偏差项不同。

我们通过用Leaky ReLU替换ReLU来解决这个问题,即使输入为负,也允许梯度传播。

我们没想到会在这个分析中会发现dead neurons,但当然最难发现的是你没有在寻找的那些。

三、梯度分析

梯度不仅可用于优化损失函数。在梯度下降中,我们计算Δ损失遵循于Δ参数。一般来说,梯度计算的是改变一个变量对另一个变量的影响。由于梯度下降需要梯度计算,像TensorFlow这样的框架包含计算梯度的函数。

我们使用梯度分析来确定我们的DNN是否可以获取数据中的长期依赖性。DNN的输入非常长:心率的4096个时间步长。我们的架构能够获取这些数据中的长期依赖关系是非常重要的。例如,心率恢复时间可以预测糖尿病。这是运动后恢复静息心率所需的时间。为了计算这个,DNN必须能够计算你的静息心率,并记住你结束锻炼的时间。

模型是否可以跟踪长期依赖关系的简单度量是检查输入数据的每个时间步对输出预测的影响。如果对后来的时间步长具有很大的影响,则该模型可能无法有效地使用之前的数据。

对于所有时间步长t,我们想要计算的梯度是相对于Δinput_t的Δoutput。以下是使用Keras和TensorFlow计算此示例的示例代码:

def gradient_output_wrt_input(model, data):
 # [:, 2048, 0] means all users in batch, midpoint timestep, 0th task (diabetes)
 output_tensor = model.model.get_layer('raw_output').output[:, 2048, 0]
 # output_tensor.shape == (num_users)
 # Average output over all users. Result is a scalar.
 output_tensor_sum = tf.reduce_mean(output_tensor)
 inputs = model.model.inputs # (num_users x num_timesteps x num_input_channels)
 gradient_tensors = tf.gradients(output_tensor_sum, inputs)
 # gradient_tensors.shape == (num_users x num_timesteps x num_input_channels)
 # Average over users
 gradient_tensors = tf.reduce_mean(gradient_tensors, axis=0)
 # gradient_tensors.shape == (num_timesteps x num_input_channels)
 # eg gradient_tensor[10, 0] is deriv of last output wrt 10th input heart rate
 # Convert to Keras function
 k_gradients = K.function(inputs=inputs, outputs=gradient_tensors)
 # Apply function to dataset
 return k_gradients([data.X])
4种调试深度神经网络的方法

在上面的代码中,我们在平均池化之前以及在中点时间步2048处测量了输出。我们使用中点而不是最后一个时间步长,因为我们的LSTM单元是双向的,这意味着对于一半的单元来说,时间步长4095实际上是第一个时间步长。我们绘制出了梯度图像。

4种调试深度神经网络的方法

Δoutput_2048/Δinput_t

注意y轴是log-scale。在时间步长2048处输出相对于输入的梯度是0.001。但是相对于输入时间步长2500的梯度要小一百万倍!通过梯度分析,我们发现这种架构无法获得长期依赖关系。

四、分析模型预测

您可能已经在分析模型预测,至少通过查看AUROC和平均绝对误差等指标,也可以运行更多分析来了解模型的行为。

例如,我们很好奇我们的DNN是否真的使用心率输入来生成预测,或者它是否严重依赖于提供的元数据——我们使用用户元数据(如:年龄和性别)初始化LSTM状态。为了理解这一点,我们将模型输出与在元数据上训练的逻辑回归模型进行了比较。

DNN需要收集一周的用户数据,因此在下面的散点图中,每个点都是一个用户周。

4种调试深度神经网络的方法

这个图推翻了我们的假设,因为这些预测并不是高度相关的。

除了综合分析之外,查看最佳赢利和亏损的例子也很有启发性。对于二元分类任务,您需要查看假阳性和假阴性(即预测离标签最远的情况)。尝试识别损失模型,然后过滤掉那些也出现在真阳性和真阴性中的模式。

一旦对损失模式有了一个假设,通过分层分析进行测试。例如,如果最大的损失都来自第一代Apple Watch,那么我们可以计算第一代Apple Watch调优集的用户集的准确性指标,并将这些指标与调优集其余部分计算的指标进行比较。

我希望这些技巧能够帮助您调试您的模型


请使用浏览器的分享功能分享到微信等