看 caffe 源码,首先从 caffe.proto 了解起,然后对caffe.cpp 进行阅读,毕竟整个庞大的 caffe 从这里的 main 函数开始。代码的阅读主要以 caffe.cpp 中的 train() 为主。以下是个人的一些笔记:
1. 关于 typedef 的一些性质 int (*BrewFunction)(); 表示,声明一个变量 BrewFunction,该变量是一个函数指针变量,指向一个无形参的函数,函数返回值为 int 型。 typedef int (*BrewFunction)(); 表示,BrewFunction是一个函数指针类型,该类型的变量是一个函数指针变量,指向一个无形参的函数,函数返回值为 int 型。 2. gflags 这是google出品的命令行参数解析库。 DEFINE_string(solver, "", "The solver definition protocol buffer text file."); 用于宏定义参数,这里定义了一个string类型的参数,该宏的三个参数含义分别为命令行参数名,参数默认值,以及参数的帮助信息。 而在使用的时候,该参数名实际为FLAG_solver,类型为string。 3. LOG(INFO) ostringstream s; for (int i = 0; i < gpus.size(); ++i) { s << (i ? ", " : "") << gpus[i]; } LOG(INFO) << "Using GPUs " << s.str(); 这个功能是输出 使用哪几个GPU。 其中LOG(INFO)是 Glog 的功能,也是google出品。相似的还有CHECK_EQ。 如: CHECK_EQ(registry.count(type), 0) << "Solver type " << type << " already registered."; 如果CHECK_EQ()不满足条件,则输出后面的内容。 4. shared_ptr指向一个Solver对象,变量名为solver,用智能指针包装。solver的初始化(注册)通过caffe::SolverRegistry<float>::CreateSolver()实现。 在solver_factory.hpp 中有 typedef std::map<string, Creator> CreatorRegistry; 在编译的时候就存在这么一个map,在编译阶段,src/caffe/solvers中的各种solver,如sgd_solver.cpp中,会通过REGISTER_SOLVER_CLASS(SGD);来声明相应的函数,并加入到CreatorRegistry这个map当中。然后再调用caffe::SolverRegistry<float>::CreateSolver(solver_param)传递solver_param来生成一个相应的对象SGDSolver。 这里再补充一下, #define REGISTER_SOLVER_CREATOR(type, creator) \ static SolverRegisterer<float> g_creator_f_##type(#type, creator<float>); \ static SolverRegisterer<double> g_creator_d_##type(#type, creator<double>) 其中,#type表示字符串,##表示字符串连接。 5. Solver的初始化 Solver在构造函数中通过Solver::Init(param)初始化对象,在Solver::Init()中又分别通过Solver::InitTrainNet();和Solver::InitTestNet();来初始化网络训练网络net_和测试网络test_nets_; 6. GPUs的使用 当只有一个GPU时,直接运行solver->Solve()开始training。 当多个GPU时,在旧版的caffe里,用的是caffe::P2PSync<float>类型的 sync 来实现CPU与GPU之间的通信,以及GPU并行运算,包括用map-reduce来实现GPU之间的通信。在新版的caffe中运用了NCCL来加速并行计算。 7. 接下来就是solver->Solve(),开始 solver 对网络的训练了。下一阶段就是看 solver.cpp 了。