C++

gtest in caffe

人工智能炼丹师
2016-09-01 / 0 评论 / 235 阅读 / 正在检测是否收录...

为了验证自己编写层的正确性,我们通过写xx_layer_test.cpp中测试层的前向和反向操作。caffe中运用Google C++ Testing Framework 来进行测试。
使用gtest(Google Test)使得程序的主体与测试独立开。使用gtest时,我们需要编写断言(assertion),判断条件是否成立。Google Test中的断言类似于函数调用,例如ASSERT_EQ(val1,val2) 判断两个数是否相等。gtest中的宏,我们只要定义即可,不用显示地调用,gtest内部通过RUN_ALL_TESTS()为我们隐式地调用了这些测试。

TEST宏

以测试n!序列(1,2!,3!,...,n!)为例,介绍 TEST宏的用法

int Factorial(int n); // Returns the factorial of n
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

TEST宏的第一个参数相同,表明它们属于同一组测试,第二个参数表明具体的测试实例(处理0输入,处理正数输入),宏的内部为需要测试的C++语句。

TEST_F宏

如果想要在不同的测试的case中访问通过一个变量或对象,我们可以定义一个测试类,将这些变量或对象定义成测试类中的成员变量。这个测试类,gtest中把它叫做Test Fixtures,并且gtest已经实现了它的父类::testing::Test, 我们只需要从该父类继承,便可节省许多代码量。
以对队列测试为例:

template <typename E> // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;
};

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

TEST_FTEST 使用的方法类似,不同之处在于,TEST_ F的第一个参数必须是类名。

TYPED_TEST 宏

这个宏与C++中的template类似,即我们希望利用相同的算法流程处理不同类型的变量。废话不多说,直接上例子。

template <typename T>
class FooTest : public ::testing::Test {
 public:
  ...
  typedef std::list<T> List;
  static T shared_;
  T value_;
};

typedef ::testing::Types<char, int, unsigned int> MyTypes;
TYPED_TEST_CASE(FooTest, MyTypes);
TYPED_TEST(FooTest, HasPropertyA) { ... }
TYPED_TEST(FooTest, HasPropertyB) { ... }

首先,与TEST_F一样,我们首先从::testing::Test 中继承,注意到我们typedef了一个类型的列表为MyTypes, 并且调用TYPED_TEST_CASE 宏告诉gtest 我们需要测试这个列表中所有的类型。

gtest的调用

同样,以一个实例来介绍,gtest在cpp文件中如何调用测试

 /*
 * gtest_main.cpp
 *
 *  Created on: 23 Oct, 2016
 *      Author: lab-xiong.jiangfeng
 */

#include "gtest/gtest.h"
#include <cmath>

double square_root(const double x){
    if(x<0){
        return -1;
    }
    return sqrt(x);
}


TEST(SquareRootTest,PositiveNos){
    EXPECT_EQ(18.0,square_root(324.0));
    EXPECT_EQ(25.4,square_root(645.16));
    EXPECT_EQ(50.332,square_root(2533.310224));
}

TEST(SquareRootTest,ZeroAndNegtiveNos){
    ASSERT_EQ(0.0,square_root(0.0));
    ASSERT_EQ(-1,square_root(-22.0));
}

int main(int argc,char **argv){
    ::testing::InitGoogleTest(&argc , argv);

    //Note that RUN_ALL_TEST automatically detects and runs all the tests define using TEST macro
    return RUN_ALL_TESTS();
}

main函数以上的内容我们都已经见过了,在main函数中,我们看到两个新的函数,InitGoogleTestRUN_ALL_TESTS 。顾名思义,InitGoogleTest 从命令行中解析参数并初始化。RUN_ALL_TESTS 自动检测我们定义的TESTTEST_F 等宏,然后测试所有的case。

gtest的基础部分就这么多,更多高级的用法可以参考googletest/AdvancedGuide.md

test_scale_layer

接下来以scale_layer为例,进一步理解caffe中的测试环节。
让我们一步一步分析test_scale_laye_.cpp的组成,首先包含一些必要的头文件

#include <algorithm>
#include <vector>

#include "gtest/gtest.h"

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/filler.hpp"
#include "caffe/layers/scale_layer.hpp"

#include "caffe/test/test_caffe_main.hpp"
#include "caffe/test/test_gradient_check_util.hpp"

这些头文件我们已经比较熟悉了,接下来就是要测试的类的定义了,

template <typename TypeParam>
class ScaleLayerTest : public MultiDeviceTest<TypeParam> {
  typedef typename TypeParam::Dtype Dtype;

乍一看,MultiDeviceTest是什么鬼。。原来它是继承自::testing::Test 的一个类,它的作用是把Caffe::set_mode() 封装到这个类的构造函数中,实现不同Device下的测试。

TYPED_TEST_CASE(ScaleLayerTest, TestDtypesAndDevices);

TYPED_TEST_CASE是不是看起来很熟悉,没错,这就是这就是上面我们提到的TYPED_TEST的用法,为了只用一个函数测试float 和double类型(在CPU和GPU下),所以TestDtypesAndDevices 自然是要测试的类型列表。TestDtypesAndDevices的定义在在test_caffe_main.hpp 中,

typedef ::testing::Types<CPUDevice<float>, CPUDevice<double>,
                         GPUDevice<float>, GPUDevice<double> >
                         TestDtypesAndDevices;

template <typename TypeParam>
struct CPUDevice {
  typedef TypeParam Dtype;
  static const Caffe::Brew device = Caffe::CPU;
};

template <typename TypeParam>
struct GPUDevice {
  typedef TypeParam Dtype;
  static const Caffe::Brew device = Caffe::GPU;
};                        

再接下来就是ScaleLayerTest的各种暴力测试,主要就是一些前向后向操作,具体内容,就自己看代码test_scale_layer.cpp慢慢消化吧。

TYPED_TEST(ScaleLayerTest, TestForwardEltwise)
TYPED_TEST(ScaleLayerTest, TestForwardEltwiseInPlace)
TYPED_TEST(ScaleLayerTest, TestBackwardEltwiseInPlace)
TYPED_TEST(ScaleLayerTest, TestForwardEltwiseWithParam)
TYPED_TEST(ScaleLayerTest, TestForwardBroadcastBegin)
TYPED_TEST(ScaleLayerTest, TestForwardBroadcastMiddle)
TYPED_TEST(ScaleLayerTest, TestForwardBroadcastMiddleInPlace)
TYPED_TEST(ScaleLayerTest, TestBackwardBroadcastMiddleInPlace)
TYPED_TEST(ScaleLayerTest, TestForwardBroadcastMiddleWithParam)
TYPED_TEST(ScaleLayerTest, TestForwardBroadcastMiddleWithParamAndBias)
TYPED_TEST(ScaleLayerTest, TestForwardBroadcastEnd)
TYPED_TEST(ScaleLayerTest, TestForwardScale)
TYPED_TEST(ScaleLayerTest, TestForwardScaleAxis2)
TYPED_TEST(ScaleLayerTest, TestGradientEltwise)
TYPED_TEST(ScaleLayerTest, TestGradientEltwiseWithParam)
TYPED_TEST(ScaleLayerTest, TestGradientBroadcastBegin)
TYPED_TEST(ScaleLayerTest, TestGradientBroadcastMiddle)
TYPED_TEST(ScaleLayerTest, TestGradientBroadcastMiddleWithParam)
TYPED_TEST(ScaleLayerTest, TestGradientBroadcastEnd)
TYPED_TEST(ScaleLayerTest, TestGradientScale)
TYPED_TEST(ScaleLayerTest, TestGradientScaleAndBias)
TYPED_TEST(ScaleLayerTest, TestGradientScaleAxis2)

THE END ~.~

2

评论 (0)

取消
粤ICP备2021042327号