很多时候算法工程师把精力更多的聚焦于当下最流行的框架。一旦出现了一些各个式样的流行框架,便迫不急待的在自己的数据集或者公共的数据集上跑。然后生成模型来验证准确率与检测速率。如果以上两者不达标便会修改数据集或者对模型调整参数。导致时下有些段子就说:“优秀的调参侠就是好的算法工程师”。暂且不论这个观点是否正确,很多时候整个深度学习过程就是这样的。
这两天突然很好奇,我用CAFFE生成的caffemodel里面到底是些什么数据。现在有很多方法可以让模型可视化。我在网上搜索了一些方法,并且做也一点实践工作,在此时写下来供大家参考。(下面的有些部分参考了其他人的博客)
Classifier类的构造函数的时候,有一个net_指针,并且执行了一个操作:
- net_->CopyTrainedLayersFrom(trained_file);
这个CopyTrainedLayersFrom函数就是从caffemodel里面载入我们需要的参数了。然后笔者就从这个函数开始挖,进入net.cpp文件:
- template <typename Dtype>
- void Net<Dtype>::CopyTrainedLayersFrom(const string trained_filename) {
- if (H5Fis_hdf5(trained_filename.c_str())) {
- CopyTrainedLayersFromHDF5(trained_filename);
- } else {
- CopyTrainedLayersFromBinaryProto(trained_filename);
- }
- }
在这里很明显执行了else下面的语句,从二进制文件中去读取了参数,然后,笔者又找到了CopyTrainedLayersFromBinaryProto函数,很巧就在CopyTrainedLayersFrom函数的下方:
- template <typename Dtype>
- void Net<Dtype>::CopyTrainedLayersFromBinaryProto(
- const string trained_filename) {
- NetParameter param;
- ReadNetParamsFromBinaryFileOrDie(trained_filename, &parm);
- CopyTrainedLayersFrom(param);
- }
在里面首先执行了一个ReadNetParamsFromBinaryFileOrDie函数,把二进制文件中的参数读到parm里面,parm是一个NetParameter类型的,NetParameter继承了实际是一个Message,Message是proto类型的,详细的笔者后话解析。然后笔者去找了一下ReadNetParamsFromBinaryFileOrDie函数,这个函数在upgrade_proto.cpp里面:
- void ReadNetParamsFromBinaryFileOrDie(const string& param_file,
- NetParameter* param) {
- CHECK(ReadProtoFromBinaryFile(param_file, param))
- << "Failed to parse NetParameter file: " << param_file;
- UpgradeNetAsNeeded(param_file, param);
- }
- bool ReadProtoFromBinaryFile(const char* filename, Message* proto) {
- int fd = open(filename, O_RDONLY);
- CHECK_NE(fd, -1) << "File not found: " << filename;
- ZeroCopyInputStream* raw_input = new FileInputStream(fd);
- CodedInputStream* coded_input = new CodedInputStream(raw_input);
- coded_input->SetTotalBytesLimit(kProtoReadBytesLimit, 536870912);
- bool success = proto->ParseFromCodedStream(coded_input);
- delete coded_input;
- delete raw_input;
- close(fd);
- return success;
- }
这个函数就比较底层了,读者朋友们可以看到,这个函数里面就使用了open函数,和一些底层的google::protobuf的数据流。在这里我们其实就明白,是先把二进制文件(caffemodel)转化成文件流,再放入proto里面。
那么,笔者大胆猜想,能读就能写。
其实io.cpp里面已经定义了这种接口:
- void WriteProtoToTextFile(const Message& proto, const char* filename) {
- int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
- FileOutputStream* output = new FileOutputStream(fd);
- CHECK(google::protobuf::TextFormat::Print(proto, output));
- delete output;
- close(fd);
- }
也就是说:先用ReadProtoFromBinaryFile函数将二进制文件读入proto里面,再将proto文件读入txt文件就行了。
如果大家要建立一个工程的话,下面会有几个文件请放在一个文件夹中:
#include <caffe/caffe.hpp> #include <google/protobuf/io/coded_stream.h> #include <google/protobuf/io/zero_copy_stream_impl.h> #include <google/protobuf/text_format.h> #include <algorithm> #include <iosfwd> #include <memory> #include <string> #include <utility> #include <vector> #include <iostream> #include "caffe/common.hpp" #include "caffe/proto/caffe.pb.h" #include "caffe/util/io.hpp" using namespace caffe; using namespace std; using google::protobuf::io::FileInputStream; using google::protobuf::io::FileOutputStream; using google::protobuf::io::ZeroCopyInputStream; using google::protobuf::io::CodedInputStream; using google::protobuf::io::ZeroCopyOutputStream; using google::protobuf::io::CodedOutputStream; using google::protobuf::Message; int main() { NetParameter proto; // ReadProtoFromBinaryFile("../examples/mnist/lenet_iter_10000.caffemodel", &proto); ReadProtoFromBinaryFile("/home/zy/zouyu/deeping/caffe/models/VGGNet/KITTI/SSD_300x300/KITTI_SSD_300x300_iter_80000.caffemodel", &proto); //把这个CAFFEMODEL通过这个函数读到proto中来。 WriteProtoToTextFile(proto, "./test.txt");//把proto在上一个函数中读到的二进制流写到test.txt中。 return 0; } ~ ~ ~ ~ ~
其对应的CMakeLists.txt文件为:
cmake_minimum_required (VERSION 2.8) project (pt_test) add_executable(pt_test pt.cpp) include_directories ( /home/zy/zouyu/deeping/caffe/include /usr/local/include /usr/local/cuda/include /usr/include ) //请确保你的头文件与链接库存的文件路径正确。 target_link_libraries(pt_test /home/zy/zouyu/deeping/caffe/build/lib/libcaffe.so /usr/lib/x86_64-linux-gnu/libglog.so /usr/lib/x86_64-linux-gnu/libboost_system.so
接下来就是编译的过程了:
执行上面的cmake . && make 后会生成一个pt_test可执行文件。当你执行过后,会生成test.txt。当你打开test.txt后,你会惊喜的发现,我原来训练的模型的相应参数都在这了。
在这个时候我的脑海中闪现一幅画面,看到了模型训练过程中图像从前端进去,经过各个卷积层、池化层、全连接层,在这一过程中利用递度下降等理论求解最小化的损失函数。最后网络停止更新,固化下来。生成了相应的MODEL,而整个训练集的DNA被以相应的形式记录了下来。如果后续要是有测试图片过来,就可以做相应的分类等工作了。
突然想到了《老子》中的一句话:“玄之又玄,众妙之门”。
相应的文件请点击如下链接: