caffe源码学习——用python定义网络时,源代码生成prototxt文件的原理

caffe源码学习——用python定义网络时,源代码生成prototxt文件的原理(1)

概述

  一般地,我们大都是使用作者定义好的网络结构来使用网络。即直接使用train.prototxt文件来使用网络,有时候为了方便修改网络参数,使用python代码来定义网络可能就便于管理。因此,该文章主要讲述了源代码中生成prototxt文件的原理。源代码主要在caffe-master/python/caffe/net_spec.py文件中。
   由于此处部分的源码较多,因此,此篇文章主要测试部分功能(layer的name,type,bottom,top变量如何写入到prototxt文件中),后续的功能将继续完善。此篇文章的主要功能如下:
    定义网络:
    这里写图片描述
    输出网络结果:
    这里写图片描述

一、作者创建layer来构建网络时的思路

(一)Layer的创建过程

   一般地,使用python代码定义网络时格式如下。按照一般的想法,不同type的layer需要定义不同的python类。即存在ImageData和Convolution等python类。

data,label=L.ImageData(source=img_list,batch_size=batch_size,ntop=2,root_folder=root+'inputdata/',transform_param=dict(scale=0.00390625))
conv1=L.Convolution(data,kernel_size=5,stride=1,num_output=20, pad=0, weight_filler=dict(type='xavier'))
pool1=L.Pooling(conv1,pool=P.Pooling.MAX,kernel_size=2,stride=2) 

   但是,作者对代码的管理更为巧妙。作者通过一个类Layer来管理所有的类型。Layer类的定义如下:

class Layer(object):
    ''' __getattr__:如果该class不包含成员name,那么在使用成员name时,通过调用此函数来实现。这里的name便是ImageData和Convolution等'''
    def __getattr__(self, name):
        def layer_fn(*args, **kwargs):
        ''' *args表示任何多个无名参数,它是一个tuple
            **kwargs表示关键字参数,它是一个dict'''
            # each layer own a member,Function,
            fn = Function(name,args,kwargs)
            if fn.ntop == 0:
                return fn
            elif fn.ntop == 1:
                return fn.tops[0]
            else:
                return fn.tops
        return layer_fn

   由此可见:当我们创建一个layer时,函数返回的实际上是Function.tops的类型。因此,我们再来看看类Function的定义(为了方便,这里只看看创建Function类时所需要的代码):

class Function(object):
    def __init__(self,type_name,inputs,params): #name args kwargs
        """
        :param type_name: the name of the Layer.
        :param inputs:    the name of the input Layer
        :param params:    the parameter of the Layer. A dict
        """
        self.type_name = type_name
        self.inputs = inputs
        self.params = params
        #初始化成员变量ntop,如果传入了该值则赋值,如果没有,则赋值为1
        self.ntop = self.params.get('ntop',1) 
        if 'ntop' in self.params:
            del self.params['ntop']
        self.in_place = self.params.get('in_place', False)
        if 'in_place' in self.params:
            del self.params['in_place']
        self.tops = tuple(Top(self,n) for n in range(self.ntop))

   因此,Function类含有的成员为:
    type_name:管理layer的类型
    inputs :管理该layer的输入层
    params :管理layer的参数
    ntop :表明layer构建时的输出有几个
    in_place :。。。。。
    tops :管理layer构建时的输出(Top类的元组)
根据Function类的全部定义可知,Function类主要是为layer提供一些函数来管理如何将layer的参数转化为prototxt的参数。反观Layer的定义,每次创建一个Layer的时候。便返回Function的tops成员,因此,Top类是枢纽。来看看Top类的定义:

class Top(object):
    def __init__(self,fn,n):
        self.fn = fn
        self.n = n
    def to_proto(self):
        return to_proto(self)
    def _to_proto(self,layers,names,autonames):
        return self.fn._to_proto(layers,names,autonames)

因此,Top类主要掌管了该Layer类的Function成员,以及用n来区分同一个Layer含有多个Top的情况。

(二)总结

   因此,Layer创建时,每一个Layer获取一个Top元组来管理该Layer。在获取的该Top元组中,每个Top都含有一个Function类来管理创建Layer时所传入的参数。又因为有的Layer创建时可能产生多个Top,因此,用参数n来区分同一个Layer所创建的多个Top,用于在写入文件时知道某个Layer的输入。

二、将构建的网络转化为字符串写入文本

(一)转化为字符串

   一般地,使用python代码构建网络并且写入文本的流程如下:

data = Layer().Hello(ntop=1) 
layer = Layer().World(data, ntop=1)
layer1 = Layer().World(layer,ntop=1)
print str(to_proto(layer1))

   由代码可知,只需将网络的最上层的Top传入函数便可将整个网络转化为字符串。因此,我们来看看to_proto(*tops)函数的定义:

def to_proto(*tops):
    layers = OrderedDict()  #create a dict of layers, null first
    autonames = Counter()   #to count the times of a top layer in a layer.A dict

    for top in tops:
        print 'to_proto',top.fn.type_name
        top.fn._to_proto(layers, {}, autonames)
    #以下的代码暂且不分析
    net = caffe_pb2.NetParameter()
    net.layer.extend(layers.values())
    return net

   可知,to_proto(*tops)函数实际调用了Function._to_proto()

class Function(object):
    """""" 
    def _to_proto(self, layers, names, autonames):
        if self in layers:
            return
        bottom_names = []
        for inp in self.inputs:
            #(1)注意这里调用了Top的_to_proto方法,实际也是调用Function的此方法,因此,直到最底层(该层没有输入)循环退出
            inp._to_proto(layers, names, autonames)
            #(3)最底层的for循环的外部执行完(也就是上层的(2)),此时的layers[inp.fn]也就是上一层的layer即caffe_pb2.LayerParameter()的对象。执行完后便退出该层的for循环而执行该层的(2)。然后再是(3)........
            bottom_names.append(layers[inp.fn].top[inp.n])
        #(2)当到达最底层没有输入层时,执行下面代码,此时self表示最底层
        layer = caffe_pb2.LayerParameter()
        layer.type = self.type_name
        layer.bottom.extend(bottom_names)#最底层时,bottom_names为空

        if self.in_place:
            layer.top.extend(layer.bottom)
        else:
            for top in self.tops:
                layer.top.append(self._get_top_name(top, names, autonames))
        layer.name = self._get_name(names, autonames)
        #以下代码暂且不看
        for k, v in six.iteritems(self.params):
            # special case to handle generic *params
            if k.endswith('param'):
                assign_proto(layer, k, v)
            else:
                try:
                    assign_proto(getattr(layer,
                        _param_names[self.type_name] + '_param'), k, v)
                except (AttributeError, KeyError):
                    assign_proto(layer, k, v)

        layers[self] = layer

  layer.top.extend(layer.bottom)的源码实际为c++代码(在protobuf的源代码中,路径为protobuf-master/python/google/protobuf/pyext/repeated_scalar_container.cc),由protobuf的编译器引申出接口(在文件),如下:
这里写图片描述
  注意此过程中的递归过程的处理。此方法的使用很值得学习。然后再来看看一个layer的name变量是如何获取的。即Function._get_name()和Function._get_top_name()方法,定义如下:

class Function(object):
    """A Function specifies a layer, its parameters, and its inputs (which
    are Tops from other layers)."""
    #autonames记录每个类型的layer的出现的次数,name=type + count
    def _get_name(self, names, autonames):
        if self not in names and self.ntop > 0:
            names[self] = self._get_top_name(self.tops[0], names, autonames)
        elif self not in names:
            autonames[self.type_name] += 1
            names[self] = self.type_name + str(autonames[self.type_name])
        return names[self]
   def _get_top_name(self, top, names, autonames):
        if top not in names:
            autonames[top.fn.type_name] += 1
            names[top] = top.fn.type_name + str(autonames[top.fn.type_name])
        return names[top]

(二)总结

  因此,在转为字符串的过程中,先是由最上层一直搜索到最下层,然后再从最下层依次往上创建layer=caffe_pb2.LayerParameter()对象,然后再往layer对象中填充字符串。注意,在暂时的层的命名方法中,名字都是根据层的类型名和出现次数来命名,后续会有灵活命名。

三、总结

  由上面的网络的创建和写入过程可知:网络的创建是自下而上的,即由最下层往最上层创建,此时,上层对于下层来说是不可见的,表现在给top命名时,top的值是该层的name,因为此时并不知道上层信息;网络的写入先是自上而下,再是自下而上的,在自上而下的过程中,为每个层创建了空的bottom_name列表,搜索到最底层后,自下而上地将bottom_name列表填满,并且同时创建layer=caffe_pb2.LayerParameter()对象,然后填写layer对象。
  注意此自下而上,然后自上而下的思想,对于层结构,模块化的结构的项目,此编程思想和技术很值得借鉴。完成了以最小的代码量解决在构建网络时,下层结构看不到上层结构的问题。

四、代码

#!/usr/bin/env python
import h5py
import numpy as np
import sys
import random
caffe_root = '/workspace/wanghao/git-repository/caffe-CN/'
sys.path.insert(0, caffe_root + 'python')

sys.path.insert(0, caffe_root+ 'python/caffe/')
import caffe
import proto
from caffe import layers as L,params as P,proto,to_proto
from proto import caffe_pb2
from collections import OrderedDict, Counter
import six
def to_proto(*tops):
    layers = OrderedDict()  #create a dict of layers, null first
    autonames = Counter()   #to count the times of a top layer in a layer.A dict

    for top in tops:
        print 'to_proto',top.fn.type_name
        top.fn._to_proto(layers, {}, autonames)
    net = caffe_pb2.NetParameter()
    net.layer.extend(layers.values())
    return net

class Top(object):
    def __init__(self,fn,n):
        self.fn = fn
        self.n = n
        #print fn ,n
    def to_proto(self):
        return to_proto(self)
    def _to_proto(self,layers,names,autonames):
        return self.fn._to_proto(layers,names,autonames)

# a class to charge of some functios ,
class Function(object):
    def __init__(self,type_name,inputs,params): #name args kwargs
        """
        :param type_name: the name of the Layer.
        :param inputs:    the name of the input Layer
        :param params:    the parameter of the Layer. A dict
        """
        self.type_name = type_name
        self.inputs = inputs
        self.params = params
        self.ntop = self.params.get('ntop',1) #get ntop in dict 'params',if not extist, return 1
        if 'ntop' in self.params:
            del self.params['ntop']
        self.in_place = self.params.get('in_place', False)
        if 'in_place' in self.params:
            del self.params['in_place']
        self.tops = tuple(Top(self,n) for n in range(self.ntop))
    # get name of the Layer
    def _get_name(self,names,autonames):
        if self not in names and self.ntop > 0:
            names[self] = self._get_top_name(self.tops[0],names,autonames)
        elif self not in names:
            autonames[self.type_name] += 1
            names[self] = self.type_name + str(autonames[self.type_name])
        return names[self]
    # get name of the Layer's top layer
    def _get_top_name(self, top, names, autonames):
        if top not in names:
            autonames[top.fn.type_name] += 1
            # top name is not the true top name,
            names[top] = top.fn.type_name + str(autonames[top.fn.type_name])
        return names[top]

    def _to_proto(self,layers, names, autonames):


        if self in layers:
            return
        bottom_names = []
        # iterator the layer's inputs.from Top to Bottom
        # if a Layer has Bottom Layer,it will go to(not add the Bottom-Layer to OrderedDict) the layer's Bottom-Layer,
        # and check  the new Layer has a Bottom-Layer or not.if has,go to the Bottom-Layer.Loop this
        #until a Layer is out of Bottom-Layer,it will end the for loop of the Layer(other topper Layer's for loop is going on)
        for inp in self.inputs:

            print inp.fn.type_name
            inp._to_proto(layers, names, autonames)
            # this will be done untill a layer without input
            #at the moment,the layer is the toppest layer,so layer[inp.fn].top=inp.fn.type_name + n
            print layers[inp.fn]
            print 'for bottom_name',layers[inp.fn].top[inp.n]
            bottom_names.append(layers[inp.fn].top[inp.n])

        print self.type_name,'out of for'
        # first, self is the most-bottom layer.fn. from Bottom to Top .
        # then,self is the output.fn of the most-bottom layer,  .....
        layer = caffe_pb2.LayerParameter()

        layer.type = self.type_name
        layer.bottom.extend(bottom_names)  #Appends all the elements in the input iterator to the container.
        if self.in_place:
            layer.top.extend(layer.bottom)
        else:
            for top in self.tops:
                print self._get_top_name(top,names,autonames)
                layer.top.append(self._get_top_name(top,names,autonames))
        layer.name = self._get_name(names,autonames)

        # deal with parameter of a layer
        for k, v in six.iteritems(self.params):

            if k.endswitch('param'):
                assign_proto(layer, k, v)
            else:
                try:
                    assign_proto(getattr(layer,
                        _param_names[self.type_name] + '_param'), k, v)
                except (ArithmeticError, KeyError):
                    assign_proto(layer, k, v)
        layers[self] = layer
        print 'layers end',layers


# when create a object of class Layer, we get a tuple of class Top
class Layer(object):
    ''' __getattr__:
        if class Layer does  not have the attribute of name, we will call the function __getattr__
        when we use the attribute of name'''
    def __getattr__(self, name):

        def layer_fn(*args, **kwargs):

            # each layer own a member,Function,
            fn = Function(name,args,kwargs)

            if fn.ntop == 0:
                return fn
            elif fn.ntop == 1:
                return fn.tops[0]
            else:
                return fn.tops
        return layer_fn

def VGG():
    data = L.Input(shape=dict(dim=[1,3,244,224]))

if __name__ == '__main__':
    #Layer()
    data = Layer().Hello(ntop=1) # the return value is a type of Top
    layer = Layer().World(data, ntop=1)
    layer1 = Layer().World(layer,ntop=1)
    #layer2 = Layer().World(layer1, ntop=1)
    print str(to_proto(layer1))
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值