深度学习常日被视为一个黑盒子,我并不反对这种不雅观点——但是你能讲清楚学到的上万参数的意义吗?

但是黑盒子的不雅观点为机器学习从业者指出了一个明显的问题:你如何调试模型?

在这篇文章中,我将会先容一些我们在 Cardiogram 中调试 DeepHeart 时用到的技能,DeepHeart 是利用来自 Apple Watch、 Garmin、和 WearOS 的数据预测疾病的深度神经网络。

在 Cardiogram 中,我们认为构建 DNN 并不是炼金术,而是工程学。

揭秘人工智能深度神经收集的4种精简调试方法

你的心脏暴露了很多你的信息。
DeepHeart 利用来自 Apple Watch、 Garmin、和 WearOS 的心率数据来预测你患糖尿病、高血压以及就寝窒息症(sleep apnea)的风险。

一、预测合成输出

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

我们在构建检测就寝窒息症的模型时利用了这个技能。
现有关于就寝窒息症筛查的文献利用白天和夜间心率标准差的差异作为筛查机制。
因此我们为每周的输入数据创建了合成输出任务:

标准差 (白天心率)—标准差 (夜间心率)

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

1. 区分白天和黑夜

2. 记住过去几天的数据

这两个都是预测就寝窒息症的先决条件,以是我们利用新架构进行实验的第一步便是检讨它是否能学习这个合成任务。

你也可以通过在合成任务上预演习网络,以半监督的形式来利用类似这样的合成任务。
当标记数据很稀缺,而你手头有大量未标记数据时,这种方法很有用。

二、可视化激活值

理解一个演习好的模型的内部机制是很难的。
你如何理解成千上万的矩阵乘法呢?

在这篇精良的 Distill 文章《Four Experiments in Handwriting with a Neural Network》中,作者通过在热图中绘制单元激活值,剖析了手写模型。
我们创造这是一个「打开 DNN 引擎盖」的好方法。

我们检讨了网络中几个层的激活值,希望能够创造一些语义属性,例如,当用户在睡觉、事情或者焦虑时,激活的单元是若何的?

用 Keras 写的从模型中提取激活值的代码很大略。
下面的代码片段创建了一个 Keras 函数 last_output_fn,该函数在给定一些输入数据的情形下,能够得到一层的输出(即它的激活值)。

from keras import backend as Kdef 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]

我们可视化了网络好几层的激活值。
在检讨第二个卷积层(一个宽为 128 的韶光卷积层)的激活值时,我们把稳到了一些奇怪的事:

卷积层的每个单元在每个韶光步长上的激活值。
蓝色的阴影代表的是激活值。

激活值竟然不是随着韶光变革的!
它们不受输入值影响,被称为「去世神经元」。

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

这个架构利用了 ReLU 激活函数,当输入是负数的时候它输出的是 0。
只管它是这个神经网络中比较浅的层,但是这确实是实际发生的事情。

在演习的某些时候,较大的梯度会把某一层的所有偏置项都变成负数,使得 ReLU 函数的输入是很小的负数。
因此这层的输出就会全部为 0,由于对小于 0 的输入来说,ReLU 的梯度为零,这个问题无法通过梯度低落来办理。

当一个卷积层的输出全部为零时,后续层的单元就会输出其偏置项的值。
这便是这个层每个单元输出一个不同值的缘故原由——由于它们的偏置项不同。

我们通过用 Leaky ReLU 更换 ReLU 办理了这个问题,前者许可梯度传播,纵然输入为负时。

我们没想到会在这次剖析中创造「去世神经元」,但最难找到的缺点是你没打算找的。

三、梯度剖析

梯度的浸染当然不止是优化丢失函数。
在梯度低落中,我们打算与Δparameter 对应的Δloss。
只管常日意义上梯度打算的是改变一个变量对另一个变量的影响。
由于梯度打算在梯度低落方法中是必需的,以是像 TensorFlow 这样的框架都供应了打算梯度的函数。

我们利用梯度剖析来确定我们的深度神经网络能否捕捉数据中的长期依赖。
DNN 的输入数据特殊长:4096 个韶光步长的心率或者计步数据。
我们的模型架构能否捕捉数据中的长期依赖非常主要。
例如,心率的规复韶光可以预测糖尿病。
这便是磨炼后规复至安歇时的心率所耗的韶光。
为了打算它,深度神经网络必须能够打算出你安歇时的心率,并记住你结束磨炼的韶光。

衡量模型能否追踪长期依赖的一种大略方法是去检讨输入数据的每个韶光步长对输出预测的影响。
如果后面的韶光步长具有特殊大的影响,则解释模型没有有效地利用早期数据。

对付所有韶光步长 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])

在上面的代码中,我们在均匀池化之前,在中点韶光步长 2048 处打算了输出。
我们之以是利用中点而不是末了的韶光步长的缘故原由是,我们的 LSTM 单元是双向的,这意味着对一半的单元来说,4095 实际上是第一个韶光步长。
我们将得到的梯度进行了可视化:

Δoutput_2048 / Δinput_t

请把稳我们的 y 轴是 log 尺度的。
在韶光步长 2048 处,与输入对应的输出梯度是 0.001。
但是在韶光步长 2500 处,对应的梯度小了一百万倍!
通过梯度剖析,我们创造这个架构无法捕捉长期依赖。

四、剖析模型预测

你可能已经通过不雅观察像 AUROC 和均匀绝对偏差这样的指标剖析了模型预测。
你还可以用更多的剖析来理解模型的行为。

例如,我们好奇 DNN 是否真的存心率输入来天生预测,或者说它的学习是不是严重依赖于所供应的元数据——我们用性别、年事这样的用户元数据来初始化 LSTM 的状态。
为了理解这个,我们将模型与在元数据上演习的 logistic 回归模型做了比拟。

DNN 模型吸收了一周的用户数据,以是不才面的散点图中,每个点代表的是一个用户周。

这幅图验证了我们的猜想,由于预测结果并不是高度干系的。

除了进行汇总剖析,查看最好和最坏的样本也是很有启示性的。
对一个二分类任务而言,你须要查看最令人震荡的假阳性和假阴性(也便是预测间隔标签最远的情形)。
考试测验鉴别丢失模式,然后过滤掉在你的真阳性和真阴性中涌现的这种模式。

一旦你对丢失模式有了假设,就通过分层剖析进行测试。
例如,如果最高丢失全部来自第一代 Apple Watch,我们可以用第一代 Apple Watch 打算我们的调优集中用户集的准确率指标,并将这些指标与在剩余调优集上打算的指标进行比较。

原文链接:https://blog.cardiogr.am/4-ways-to-debug-your-deep-neural-network-e5edb14a12d7