最近打算看一看caffe实现的源码,因为发现好多工作都是基于改动网络来实现自己的的目的。比如变更目标函数以及网络结构,以实现图片风格转化或者达到更好的效果。
深度学习框架 https://mp.weixin.qq.com/s?__biz=MzI1NTE4NTUwOQ==&mid=2650325746&idx=1&sn=378e1adc20bb9f4e388e1bd648707026&chksm=f235a5f8c5422ceee84aa4dff2b5c025397e3a4217a7dcba252c2975dcba5cdac3e621bf16d8&mpshare=1&scene=1&srcid=1212R1BswFAT6Fk6wVXdI7Fb&pass_ticket=q%2B7V8do%2BkSgvBI1mFPt5tyOwcZQNfmKYM6zrf6DOq4U%3D#rd
caffe.bin
这个是我们在训练网络时用到的可执行文件,其对应的源码为,其中main()函数首先对输入参数进行解析,这里边用到了google的工具包gflags,参数会把linux下输入的 ./build/tools/caffe.bin train --solver=examples/mnist/lenet_solver.prototxt -gpu all
识别为两部分,./build/tools/caffe.bin和train,然后前面带”-“的自动识别为参数。之后在main()函数中调用GetBrewFunction()函数启动对应的函数(train,test,time),这些函数是通过RegisterBrewFunction()在编译中把指向其的指针保存到一个map结构中。
接着,在train()函数中,首先读取指定网络hyper parameters以及网络结构,这是由.proto格式的文件定义的,这是一种类似xml和json的数据交换格式,且google有对应的工具包来生成对应的处理函数以及类。 caffe::ReadSolverParamsFromTextFileOrDie(FLAGS_solver, &solver_param);
其中FLAGS_solver是.proto文件的地址,SolverParameter的定义在src/caffe/proto/caffe.proto中,在编译时,会产生一个caffe.pb.cc和caffe.pb.h文件,对应的就是其产生的类以及一些函数。在读取到这些参数后,开始设置GPU/CPU模式,然后调用 solver(caffe::SolverRegistry<float>::CreateSolver(solver_param));
函数初始化网络。
include/caffe/solver_factory.hpp
在上面调用的函数中,CreateSolver()函数是产生一个CreatorRegistry的对象,这是一个map结构,其key值是string,指定优化算法,value是一个指向Solver类对象的指针。这个函数在返回的时候,会调用Solver类的构造函数产生一个Solver类对象。然后上面的Solver类对象solver通过默认的拷贝构造函数产生这个solver对象。
src/caffe/solver.cpp
在Solver类的构造函数中,其会调用Init()函数,这个函数中会调用其两个成员函数InitTrainNet()和InitTestNets()。在Solver这个类中,有Net的类对象作为其成员变量,包括用于训练的网络net_和用于测试的网络test_nets_。在上面的两个函数内,这些对象会通过reset()函数复制由 Net(const NetParameter& param, const Net* root_net);
构造函数产生的Net类对象。
src/caffe/net.cpp
在Net类的构造函数中,会调用类成员函数 Init(const NetParameter& in_param);
其会首先对网络的每层做一次过滤FilterNet(),比如对于训练网络来说,accuracy网络层是不必要的,而起始的测试(训练)网络的输入层对训练(测试)网络来说是不必要的。接着网络调用在++src/caffe/util/insert_splits.cpp++中定义的InsertSplits()函数来根据情况产生一层网络。就是当网络层A的输出会作为网络层B,C的输入,那么通过InsertSplits()函数会产生一层split层,就是对共享输入的层,在这些层前面加入一层split(参考)。
之后网络开始根据层数的多少设置输入bottom和输出top的大小,它们是存放在vector容器中的blob数据格式,其定义在blob类中,此地按下不表。对于每一层,用一个for循环,设置层与层之间的连接,以及存放每层得到的残差的内存空间,哪些层需要后向传播,并统计内存消耗等。然后从后往前遍历一遍网络,统计哪些blobs会涉及到网络损失的计算。并且对于剩余的为处理分配的blobs,认定其为输出。最后可能某些层之间会共享w和b的权重以及方差,所以需要调用ShareWeights()函数开启一下,至此网络初始化完成。