为了验证自己编写层的正确性,我们通过写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()
为我们隐式地调用了这些测试。
以测试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++语句。
如果想要在不同的测试的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_F
与TEST
使用的方法类似,不同之处在于,TEST_ F
的第一个参数必须是类名。
这个宏与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在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函数中,我们看到两个新的函数,InitGoogleTest
和RUN_ALL_TESTS
。顾名思义,InitGoogleTest
从命令行中解析参数并初始化。RUN_ALL_TESTS
自动检测我们定义的TEST
、TEST_F
等宏,然后测试所有的case。
gtest的基础部分就这么多,更多高级的用法可以参考googletest/AdvancedGuide.md
接下来以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 ~.~
评论 (0)