在过去的一年里,我大部分的工作时间都花在了深度学习的研究和实习上。那一年我犯了很多大错误,这些错误不仅帮助我理解了机器学习,还帮助我理解了如何正确、稳健地设计这些系统。我在GoogleBrain学到的主要原则之一是单元测试可以成就或破坏算法,并为您节省数周的调试和培训时间。但是,似乎没有关于如何为神经网络代码编写单元测试的可靠在线教程。甚至像OpenAI这样的地方也只是通过盯着代码的每一行并试图思考它导致错误的原因来发现错误。显然,我们大多数人都没有时间,所以希望本教程能帮助您开始理智地测试您的系统!让我们从一个简单的例子开始。尝试找出这段代码中的错误。defmake_convnet(input_image):net=slim.conv2d(input_image,32,[11,11],scope="conv1_11x11")net=slim.conv2d(input_image,64,[5,5],scope="conv2_5x5")net=slim.max_pool2d(net,[4,4],stride=4,scope='pool1')net=slim.conv2d(input_image,64,[5,5],scope="conv3_5x5")net=slim.conv2d(input_image,128,[3,3],scope="conv4_3x3")net=slim.max_pool2d(net,[2,2],scope='pool2')net=slim.conv2d(input_image,128,[3,3],scope="conv5_3x3")net=slim.max_pool2d(net,[2,2],scope='pool3')net=slim.conv2d(input_image,32,[1,1],scope="conv6_1x1")returnnet你看到了吗?网实际上并没有堆积起来。在编写这段代码时,我复制并粘贴了slim.conv2d(...)行,只修改了内核大小,而不是实际输入。我不好意思说这件事一周前发生在我身上……但这是一个重要的教训!由于一些原因,这些错误很难被捕获。此代码不会崩溃、产生错误,甚至不会减慢速度。该网络仍在运行,损失仍将下降。几个小时后,这些值会收敛,但结果非常糟糕,以至于您对需要修复的内容摸不着头脑。当您唯一的反馈是最终的验证错误时,您唯一需要搜索的地方就是您的整个网络架构。不用说,您需要一个更好的系统。那么,在进行完整的多日培训之前,我们如何真正利用这个机会呢?关于这一点,最容易注意到的是层值实际上并没有转到函数之外的任何其他张量。假设我们有某种类型的损失和优化器,这些张量永远不会被优化,所以它们总是有它们的默认值。我们可以通过简单的训练步骤和前后比较来检测它。deftest_convnet():image=tf.placeholder(tf.float32,(None,100,100,3)model=Model(image)sess=tf.Session()sess.run(tf.global_variables_initializer())before=sess.run(tf.trainable_variables())_=sess.run(model.train,feed_dict={image:np.ones((1,100,100,3)),})after=sess.run(tf.trainable_variables())forb,a,ninzip(before,after):#Makesuresomethingchanged.assert(b!=a).any()在不到15行代码中,我们现在验证至少我们创建的所有变量都经过训练。这个测试非常简单,超级有效.假设我们解决了之前的问题,现在我们要开始添加一些批量归一化。看看你能不能发现这个错误。defmake_convnet(image_input):#Trytonormalizetheinputbeforeconvolutingnet=slim.batch_norm(image_input)net=slim.conv2d(net,32,[11,11],scope="conv1_11x11")net=slim.conv2d(net,64,[5,5],scope="conv2_5x5")net=slim.max_pool2d(net,[4,4],stride=4,scope='pool1')net=slim.conv2d(net,64,[5,5],scope="conv3_5x5")net=slim.conv2d(net,128,[3,3],scope="conv4_3x3")net=slim.max_pool2d(net,[2,2],scope='pool2')net=slim。conv2d(net,128,[3,3],scope="conv5_3x3")net=slim.max_pool2d(net,[2,2],scope='pool3')net=slim.conv2d(net,32,[1,1],scope="conv6_1x1")returnnet看到了吗?这是非常微妙的。可以看到在tensorflowbatch_norm中,is_training的默认值是False,所以加入这行代码并不会在训练时对你的输入进行归一化!值得庆幸的是,我们编写的最后一个单元测试会立即发现这个问题!(我知道,因为这是三天前发生在我身上的。)另一个例子。这实际上来自我有一天看到的一篇文章(https://www.reddit.com/r/MachineLearning/comments/6qyvvg/p_tensorflow_response_is_making_no_sense/)。我不会详细介绍,但基本上这个人想要创建一个输出范围为(0,1)的分类器。classModel:def__init__(self,input,labels):"""ClassifiermodelArgs:input:Inputtensorofsize(None,input_dims)label:Labeltensorofsize(None,1).Shouldbeoftypetf.int32."""prediction=self.make_network(input)#Predictionsizeis(None,1).self.loss=tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labelslabels=labels)self.train_op=tf.train.AdamOptimizer().minimize(self.loss)注意到这个错误了吗?这真的很难提前发现,并可能导致超级混乱的结果。基本上,这里发生的是预测只有一个输出,当您对其应用softmax交叉熵时,它的损失始终为0。测试这一点的一种简单方法是确保损失不为0。deftest_loss():in_tensor=tf.placeholder(tf.float32,(None,3))labels=tf.placeholder(tf.int32,None,1))model=Model(in_tensor,labels)sess=tf.Session()loss=sess.run(model.loss,feed_dict={in_tensor:np.ones(1,3),labels:[[1]]})assertloss!=0另一个好的测试与我们的第一个测试类似,但相反。您可以确保只训练您要训练的变量。以GAN为例。一个常见的错误是在优化时不小心忘记设置要训练的变量。像这样的代码经常发生。classGAN:def__init__(self,z_vector,true_images):#Pretendtheseareimplemented.withtf.variable_scope("gen"):self.make_geneator(z_vector)withtf.variable_scope("des"):self.make_descriminator(true_images)opt=tf.AdamOptimizer()train_descrim=opt.minimize(self.descrim_loss)train_gen=opt.minimize(self.gen_loss)这里最大的问题是优化器有一个默认设置来优化所有变量。在像GAN这样的高级架构中,这对你所有的训练时间来说都是死刑。但是,您可以通过编写如下测试轻松发现这些错误:deftest_gen_training():model=Modelsess=tf.Session()gen_vars=tf.get_collection(tf.GraphKeys.VARIABLES,scope='gen')des_vars=tf。get_collection(tf.GraphKeys.VARIABLES,scope='des')before_gen=sess.run(gen_vars)before_des=sess.run(des_vars)#Trainthegenerator.sess.run(model.train_gen)after_gen=sess.run(gen_vars)after_des=sess.run(des_vars)#Makesurethegeneratorvariableschanged.forb,ainzip(before_gen,after_gen):assert(a!=b).any()#MakesuredescriminatordidNOTchange.forb,ainzip(before_des,after_des):assert(a==b)。all()可以为判别器编写一个非常相似的测试。同样的测试也可以用于许多强化学习算法。许多行为批评模型都有单独的网络,需要针对不同的损失进行优化。以下是我建议您测试的一些模式。使测试具有确定性。如果测试以奇怪的方式失败并且您永远无法重现该错误,那就太糟糕了。如果您真的想要随机输入,请确保使用种子随机数,以便您可以轻松地重新运行测试。保持测试简短。不要使用单元测试来训练收敛和检查验证集。这样做是在浪费你的时间。确保在每次测试之间重置计算图。总之,还有很多方法可以测试这些黑盒算法!花一个小时编写测试可以节省您重新运行经过训练的模型的天数,并且可以大大提高您的研究效率。因为我们的实施有缺陷而不得不放弃完美的想法不是很糟糕吗?这份清单显然并不全面,但它是一个良好的开端!
