如何对机器学习做单元测试

作者: AI公园 2019-12-18 10:25:12

在过去的一年里,我把大部分的工作时间都花在了深度学习研究和实习上。那一年,我犯了很多大错误,这些错误不仅帮助我了解了ML,还帮助我了解了如何正确而稳健地设计这些系统。我在谷歌Brain学到的一个主要原则是,单元测试可以决定算法的成败,可以为你节省数周的调试和训练时间。

机器学习/单元测试

然而,在如何为神经网络代码编写单元测试方面,似乎没有一个可靠的在线教程。即使是像OpenAI这样的地方,也只是通过盯着他们代码的每一行,并试着思考为什么它会导致bug来发现bug的。显然,我们大多数人都没有这样的时间,所以希望本教程能够帮助你开始理智地测试你的系统!

让我们从一个简单的例子开始。试着找出这段代码中的错误。

  1. def make_convnet(input_image): 
  2.     net = slim.conv2d(input_image, 32, [11, 11], scope="conv1_11x11"
  3.     net = slim.conv2d(input_image, 64, [5, 5], scope="conv2_5x5"
  4.     net = slim.max_pool2d(net, [4, 4], stride=4scope='pool1'
  5.     net = slim.conv2d(input_image, 64, [5, 5], scope="conv3_5x5"
  6.     net = slim.conv2d(input_image, 128, [3, 3], scope="conv4_3x3"
  7.     net = slim.max_pool2d(net, [2, 2], scope='pool2'
  8.     net = slim.conv2d(input_image, 128, [3, 3], scope="conv5_3x3"
  9.     net = slim.max_pool2d(net, [2, 2], scope='pool3'
  10.     net = slim.conv2d(input_image, 32, [1, 1], scope="conv6_1x1"
  11.     return net 

你看到了吗?网络实际上并没有堆积起来。在编写这段代码时,我复制并粘贴了slim.conv2d(…)行,并且只修改了内核大小,而没有修改实际的输入。

我很不好意思地说,这件事在一周前就发生在我身上了……但这是很重要的一课!由于一些原因,这些bug很难捕获。

  • 这段代码不会崩溃,不会产生错误,甚至不会变慢。
  • 这个网络仍在运行,损失仍将下降。
  • 几个小时后,这些值就会收敛,但结果却非常糟糕,让你摸不着头脑,不知道需要修复什么。

当你唯一的反馈是最终的验证错误时,你惟一需要搜索的地方就是你的整个网络体系结构。不用说,你需要一个更好的系统。

那么,在我们进行完整的多日训练之前,我们如何真正抓住这个机会呢?关于这个最容易注意到的是层的值实际上不会到达函数外的任何其他张量。假设我们有某种类型的损失和一个优化器,这些张量永远不会得到优化,所以它们总是有它们的默认值。

我们可以通过简单的训练步骤和前后对比来检测它。

  1. def test_convnet(): 
  2.   image = tf.placeholder(tf.float32, (None, 100, 100, 3) 
  3.   model = Model(image) 
  4.   sess = tf.Session() 
  5.   sess.run(tf.global_variables_initializer()) 
  6.   before = sess.run(tf.trainable_variables()) 
  7.   _ = sess.run(model.train, feed_dict={ 
  8.                image: np.ones((1, 100, 100, 3)), 
  9.                }) 
  10.   after = sess.run(tf.trainable_variables()) 
  11.   for b, a, n in zip(before, after): 
  12.       # Make sure something changed. 
  13.       assert (b != a).any() 

在不到15行代码中,我们现在验证了至少我们创建的所有变量都得到了训练。

这个测试超级简单,超级有用。假设我们修复了前面的问题,现在我们要开始添加一些批归一化。看看你能否发现这个bug。

  1. def make_convnet(image_input): 
  2.         # Try to normalize the input before convoluting 
  3.         net = slim.batch_norm(image_input) 
  4.         net = slim.conv2d(net, 32, [11, 11], scope="conv1_11x11"
  5.         net = slim.conv2d(net, 64, [5, 5], scope="conv2_5x5"
  6.         net = slim.max_pool2d(net, [4, 4], stride=4scope='pool1'
  7.         net = slim.conv2d(net, 64, [5, 5], scope="conv3_5x5"
  8.         net = slim.conv2d(net, 128, [3, 3], scope="conv4_3x3"
  9.         net = slim.max_pool2d(net, [2, 2], scope='pool2'
  10.         net = slim.conv2d(net, 128, [3, 3], scope="conv5_3x3"
  11.         net = slim.max_pool2d(net, [2, 2], scope='pool3'
  12.         net = slim.conv2d(net, 32, [1, 1], scope="conv6_1x1"
  13.         return net 

你看到了吗?这个非常微妙。您可以看到,在tensorflow batch_norm中,is_training的默认值是False,所以添加这行代码并不能使你在训练期间的输入正常化!值得庆幸的是,我们编写的最后一个单元测试将立即发现这个问题!(我知道,因为这是三天前发生在我身上的事。)

再看一个例子。这实际上来自我一天看到的一篇文章

(https://www.reddit.com/r/MachineLearning/comments/6qyvvg/p_tensorflow_response_is_making_no_sense/)。我不会讲太多细节,但是基本上这个人想要创建一个输出范围为(0,1)的分类器。

  1. class Model: 
  2.   def __init__(self, input, labels): 
  3.     """Classifier model 
  4.     Args: 
  5.       input: Input tensor of size (None, input_dims) 
  6.       label: Label tensor of size (None, 1).  
  7.         Should be of type tf.int32. 
  8.     """ 
  9.     prediction = self.make_network(input) 
  10.     # Prediction size is (None, 1). 
  11.     self.loss = tf.nn.softmax_cross_entropy_with_logits( 
  12.         logits=predictionlabelslabels=labels) 
  13.     self.train_op = tf.train.AdamOptimizer().minimize(self.loss) 

注意到这个错误吗?这是真的很难提前发现,并可能导致超级混乱的结果。基本上,这里发生的是预测只有一个输出,当你将softmax交叉熵应用到它上时,它的损失总是0。

一个简单的测试方法是确保损失不为0。

  1. def test_loss(): 
  2.   in_tensor = tf.placeholder(tf.float32, (None, 3)) 
  3.   labels = tf.placeholder(tf.int32, None, 1)) 
  4.   model = Model(in_tensor, labels) 
  5.   sess = tf.Session() 
  6.   loss = sess.run(model.loss, feed_dict={ 
  7.     in_tensor:np.ones(1, 3), 
  8.     labels:[[1]] 
  9.   }) 
  10.   assert loss != 0 

另一个很好的测试与我们的第一个测试类似,但是是反向的。你可以确保只有你想训练的变量得到了训练。以GAN为例。出现的一个常见错误是在进行优化时不小心忘记设置要训练的变量。这样的代码经常发生。

  1. class GAN: 
  2.   def __init__(self, z_vector, true_images): 
  3.     # Pretend these are implemented. 
  4.     with tf.variable_scope("gen"): 
  5.       self.make_geneator(z_vector) 
  6.     with tf.variable_scope("des"): 
  7.       self.make_descriminator(true_images) 
  8.     opt = tf.AdamOptimizer() 
  9.     train_descrim = opt.minimize(self.descrim_loss) 
  10.     train_gen = opt.minimize(self.gen_loss) 

这里最大的问题是优化器有一个默认设置来优化所有变量。在像GANs这样的高级架构中,这是对你所有训练时间的死刑判决。但是,你可以通过编写这样的测试来轻松地发现这些错误:

  1. def test_gen_training(): 
  2.   model = Model 
  3.   sess = tf.Session() 
  4.   gen_vars = tf.get_collection(tf.GraphKeys.VARIABLES, scope='gen'
  5.   des_vars = tf.get_collection(tf.GraphKeys.VARIABLES, scope='des'
  6.   before_gen = sess.run(gen_vars) 
  7.   before_des = sess.run(des_vars) 
  8.   # Train the generator. 
  9.   sess.run(model.train_gen) 
  10.   after_gen = sess.run(gen_vars) 
  11.   after_des = sess.run(des_vars) 
  12.   # Make sure the generator variables changed. 
  13.   for b,a in zip(before_gen, after_gen): 
  14.     assert (a != b).any() 
  15.   # Make sure descriminator did NOT change. 
  16.   for b,a in zip(before_des, after_des): 
  17.     assert (a == b).all() 

可以为鉴别器编写一个非常类似的测试。同样的测试也可以用于许多强化学习算法。许多行为-批评模型有单独的网络,需要根据不同的损失进行优化。

下面是一些我推荐你进行测试的模式。

  • 让测试具有确定性。如果一个测试以一种奇怪的方式失败,却永远无法重现这个错误,那就太糟糕了。如果你真的想要随机输入,确保使用种子随机数,这样你就可以轻松地重新运行测试。
  • 保持测试简短。不要使用单元测试来训练收敛性并检查验证集。这样做是在浪费自己的时间。
  • 确保你在每个测试之间重置了计算图。

总之,这些黑箱算法仍然有很多方法需要测试!花一个小时写一个测试可以节省你几天的重新运行训练模型,并可以大大提高你的研究效率。因为我们的实现有缺陷而不得不放弃完美的想法,这不是很糟糕吗?

这个列表显然不全面,但它是一个坚实的开始!

机器学习 单元测试 神经网络
上一篇:新手机器学习工程师容易犯的错误Top6 下一篇:二十载“中国芯” 新时代再启程
评论
取消
暂无评论,快去成为第一个评论的人吧

更多资讯推荐

数据机器学习在故障检测中的应用

本文将简要介绍几种在故障诊断领域广泛应用的机器学习技术及其各自的应用方向,并对每种技术的优缺点进行简单分析。包括:贝叶斯网络(BN),人工神经网络(ANN),支持向量机(SVM)和隐马尔可夫模型(HMM)技术。

交能网 ·  1天前
人工智能如何改造旅游业

旅游业正在慢慢地将人工智能融入到行业当中,并为游客提供个性化定制体验。在人工智能的帮助下,旅游业的业务流程和客户服务都发生了改变。

佚名 ·  1天前
治愈大脑,人机共生,马斯克为“脑机接口”辩护

随着今年年初特斯拉中国工厂正式开始交付特斯拉 model3 型号电动汽车,特斯拉的股值不断飙升,与此同时特斯拉和马斯克也在新闻媒体上赚足了眼球。

学术君 ·  3天前
如何在Kaggle上打比赛,带你进行一次完整流程体验

Kaggle是一个磨练您的机器学习和数据科学技能的好地方,您可以将自己与他人进行比较,并学习新的技术。在这篇文章中,我们利用一个典型的例子,来给大家演示如何参加Kaggle竞赛。

机器学习与数据分析 ·  3天前
一文读懂即将引爆的TinyML:在边缘侧实现超低功耗机器学习

人工智能AI正在加快速度从“云端”走向“边缘”,进入到越来越小的物联网设备中。在终端和边缘侧的微处理器上,实现的机器学习过程,被称为微型机器学习,即TinyML。

物女王 ·  3天前
谷歌发布TyDi QA语料库,涵盖11种不同类型语言

为了鼓励对多语言问答技术的研究,谷歌发布了 TyDi QA,这是一个涵盖了 11 种不同类型语言的问答语料库。

Jonathan Clark ·  4天前
机器学习所需的工程量未来会大大减少 精选

未来,构建 ML 产品将更加有趣,并且这些系统会工作得更好。随着 ML 自动化工具的不断改进,数据科学家和 ML 工程师将把更多的时间花在构建优秀的模型上,而花在与生产级 ML 系统相关的繁琐但必要的任务上的时间会更少。

David LiCause ·  4天前
意料之外 情理之中:解读Gartner 2020年数据科学和机器学习平台魔力象限

最近Gartner发布了数据科学和机器学习(DSML)平台魔力象限报告。数据科学、机器学习和人工智能的市场格局极为分散,竞争激烈且难以理解。Gartner尝试根据明确定义的标准对厂商进行了排名。

佚名 ·  2020-02-21 17:23:21
Copyright©2005-2020 51CTO.COM 版权所有 未经许可 请勿转载