美文网首页Caffe2程序员
Caffe2 的基本数据结构(Basics of Caffe2

Caffe2 的基本数据结构(Basics of Caffe2

作者: 少侠阿朱 | 来源:发表于2017-05-02 18:09 被阅读1255次

    这篇文章主要介绍Caffe2的基本数据结构:

    • Workspaces
    • Operators
    • Nets

    在开始之前最好先阅读以下Intro Turorial
    首先,导入caffe2。其中coreworksapce模块,这是必须的两个模块。如果你要使用Caffe2生成的protocol buffers,那么你也需要从caffe2_pb2中导入caffe2_pb2模块。

    # We'll also import a few standard python libraries
    from matplotlib import pyplot
    import numpy as np
    import time
    
    # These are the droids you are looking for.
    from caffe2.python import core, workspace
    from caffe2.proto import caffe2_pb2
    

    如果你看到一些警告:Caffe2不支持GPU。这说明,你正在跑的Caffe2仅仅编译了CPU模式。不用担心,Caffe2在CPU上也是可以运行的。

    Workspaces

    让我们先来介绍Workspace,它包含了所有数据。如果你熟悉Matlabworksapce包含了所有你创建的blob并保存在内存中。现在,让我们考虑一个N维的blob,blob和numpy的矩阵很像,但是它是连续的。接下来,我们将展示blob实际上是一种能指向任何C++类型对象的指针。下面,我们来看看接口是什么样的的。

    Blobs()函数可以打印workspace里面所有的blobs。HasBlob则用于查询worksapce里面是否存在某个blob。不过,目前为止,我们的workspace里面没有任何东西。

    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    print("Workspace has blob 'X'? {}".format(workspace.HasBlob("X")))
    

    FeedBlob()函数用于向worksapce里面传递blob。

    X = np.random.randn(2, 3).astype(np.float32)
    print("Generated X from numpy:\n{}".format(X))
    workspace.FeedBlob("X", X)
    

    打印出来的X如下:

    Generated X from numpy:
    [[-0.56927377 -1.28052795 -0.95808828]
     [-0.44225693 -0.0620895  -0.50509363]]
    

    让我们看一下workspace里面的blob是什么样的。

    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    print("Workspace has blob 'X'? {}".format(workspace.HasBlob("X")))
    print("Fetched X:\n{}".format(workspace.FetchBlob("X")))
    

    输出如下:

    Current blobs in the workspace: [u'X']
    Workspace has blob 'X'? True
    Fetched X:
    [[-0.56927377 -1.28052795 -0.95808828]
     [-0.44225693 -0.0620895  -0.50509363]]
    

    接着验证两个矩阵是否相等:

    np.testing.assert_array_equal(X, workspace.FetchBlob("X"))
    

    注意,如果你访问一个不存在的blob,将会引发一个错误:

    try:
        workspace.FetchBlob("invincible_pink_unicorn")
    except RuntimeError as err:
        print(err)
    

    错误输出如下:

    [enforce fail at pybind_state.cc:441] gWorkspace->HasBlob(name).
    

    另外,有一个你目前可能还用不上的东西:你可以定义两个不同名字的workspace,并且在他们之间切换。不同workspace的bolb是相互分离的。你可以通过CurrentWorkspace()函数来访问当前的workspace。下面演示了如何切换不同的workspace和创建新的workspace。

    print("Current workspace: {}".format(workspace.CurrentWorkspace()))
    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    
    # 切换到`gutentag` workspace,第二个参数`True`表示,如果`gutentag`不存在,则创建一个。
    workspace.SwitchWorkspace("gutentag", True)
    
    # 现在重新打印,注意到当前的workspace是`gutentag`,并且其中不包含任何东西。
    print("Current workspace: {}".format(workspace.CurrentWorkspace()))
    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    

    程序输出:

    Current workspace: default
    Current blobs in the workspace: ['X']
    Current workspace: gutentag
    Current blobs in the workspace: []
    

    重新切换回到defaultworkspace

    workspace.SwitchWorkspace("default")
    print("Current workspace: {}".format(workspace.CurrentWorkspace()))
    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    

    并有如下输出:

    Current workspace: default
    Current blobs in the workspace: ['X']
    

    最后,调用ResetWorkspace()函数可以清空当前的workspace的所有东西

    workspace.ResetWorkspace()
    

    Operators

    Caffe2中,operator就像函数一样。从C++的角度理解,operator全部从一个通用的接口继承而来,它们通过类型进行注册,所以,我们可以在运行时调用不同的操作。operator的接口定义在caffe2/proto/caffe2.proto文件中。Operator根据输出产生相应的输出。
    记住,在Caffe2的Python接口中,当我们说“创建一个operator”时,程序并没有跑起来,它只是创建了关于这个operator的protocol buffere,也就是定义了这个operator,但还没执行。之后,这个operator才会传递给C++接口禁止执行。如果你不明白什么是protobuf,那么你可以看下这个链接.
    **1. **
    下面看一个实际例子:

    # Create an operator.
    op = core.CreateOperator(
        "Relu", # The type of operator that we want to run
        ["X"], # 输入 blobs 的名字的列表
        ["Y"], # A list of 输出 blobs by their names
    )
    # and we are done!
    

    我们之前说到,创建op(operator),事实上只是创建了一个protobuf对象。我们可以查看它的内容。

    print("Type of the created op is: {}".format(type(op)))
    print("Content:\n")
    print(str(op))
    

    输出如下:

    Type of the created op is: <class 'caffe2.proto.caffe2_pb2.OperatorDef'>
    Content:
    input: "X"
    output: "Y"
    name: ""
    type: "Relu"
    

    现在跑起这个operator,我们首先需要向workspace中传入数据X,然后简单的调用workspace.RunOperatorOnce(operator)函数就可以。

    workspace.FeedBlob("X", np.random.randn(2, 3).astype(np.float32))
    workspace.RunOperatorOnce(op)
    

    执行完后,让我们检查下这个operator是否正确操作。在这个操作中我们使用的是Relu函数。Relu函数在输入小于0时,取0,在输入大于0时,保持不变。

    print("Current blobs in the workspace: {}\n".format(workspace.Blobs()))
    print("X:\n{}\n".format(workspace.FetchBlob("X")))
    print("Y:\n{}\n".format(workspace.FetchBlob("Y")))
    print("Expected:\n{}\n".format(np.maximum(workspace.FetchBlob("X"), 0)))
    

    输出如下,可以看到输出Y和你期望值一样,这个operator正确跑起来了:

    Current blobs in the workspace: ['X', 'Y']
    X:
    [[ 1.03125858  1.0038228   0.0066975 ]
     [ 1.33142471  1.80271244 -0.54222912]]
    Y:
    [[ 1.03125858  1.0038228   0.0066975 ]
     [ 1.33142471  1.80271244  0.        ]]
    
    Expected:
    [[ 1.03125858  1.0038228   0.0066975 ]
     [ 1.33142471  1.80271244  0.        ]]
    

    2.
    当然Operator也支持选项参数。选项参数通过key-value对确定。下面是一个简单的例子:创建一个tensor并且用高斯随机值填充它。

    op = core.CreateOperator(
        "GaussianFill",
        [], # GaussianFill does not need any parameters.
        ["Z"],
        shape=[100, 100], # shape argument as a list of ints.
        mean=1.0,  # mean as a single float
        std=1.0, # std as a single float
    )
    print("Content of op:\n")
    print(str(op))
    

    看看输出:

    Content of op:
    output: "Z"
    name: ""
    type: "GaussianFill"
    arg {
      name: "std"
      f: 1.0
    }
    arg {
      name: "shape"
      ints: 100
      ints: 100
    }
    arg {
      name: "mean"
      f: 1.0
    }
    

    然后我们跑起这个op,看看事情是否如期。

    workspace.RunOperatorOnce(op)
    temp = workspace.FetchBlob("Z")
    pyplot.hist(temp.flatten(), bins=50)
    pyplot.title("Distribution of Z")
    
    image.png

    没错,就是这样。

    Nets

    Net其实是多个operator的集合,就像写程序时一行一行的命令。

    让我们创建一个等价于下面Python代码的网络。

    X = np.random.randn(2, 3)
    W = np.random.randn(5, 3)
    b = np.ones(5)
    Y = X * W^T + b
    

    Caffe2中的core.net是对NetDef protocol buffer的一个封装类。当创建一个网络时,这个对象完全是空的,除了拥有它的名字信息外。

    net = core.Net("my_first_net")
    print("Current network proto:\n\n{}".format(net.Proto()))
    
    Current network proto:
    name: "my_first_net"
    

    接着创建一个blob,命名为“X”,使用高斯函数进行填充。

    X = net.GaussianFill([], ["X"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)
    print("New network proto:\n\n{}".format(net.Proto()))
    

    这时网络的结构如下

    New network proto:
    name: "my_first_net"
    op {
      output: "X"
      name: ""
      type: "GaussianFill"
      arg {
        name: "std"
        f: 1.0
      }
      arg {
        name: "run_once"
        i: 0
      }
      arg {
        name: "shape"
        ints: 2
        ints: 3
      }
      arg {
        name: "mean"
        f: 0.0
      }
    }
    

    聪明的读者肯定想起了我们之前提到的core.CreateOperator()。事实上,当我们有了一个net,我们可以直接创建一个operator然后通过Python接口加到net中去。比如,你调用了net.SomeOp,这里的SomeOp是一个注册了的operator的字符串,因此上面的操作和下面等效。

    op = core.CreateOperator("SomeOp", ...)
    net.Proto().op.append(op)
    

    译者注:
    比如在我用op = core.CreateOperator("GaussianFill",[], ["Z"],shape=[100, 100],mean=1.0, std=1.0)创建了一个op,op的type为“GaussianFill”,这是一个注册了的类型。然后再调用net.Proto().op.append(op)把这个op添加到网络中去。
    以上的操作可以同过net来调用直接实现。直接使用op的type string---“GaussianFill”作为函数名字,net.GaussianFill([], ["X"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)。

    当然,读者可能感到困惑,X是什么?X是一个 BlobReference,这个引用包含两样东西:
    - 名字,可以通过str(X)来访问得到
    - 它是哪个net创建的,记录在其中的变量_from_net
    现在让我们验证它。同样记住,我们还没有跑任何东西,所以X只是个符号,里面什么也没有。别只望它会输出什么值。

    print("Type of X is: {}".format(type(X)))
    print("The blob name is: {}".format(str(X)))
    
    Type of X is: <class 'caffe2.python.core.BlobReference'>
    The blob name is: X
    

    让我们继续创建W和b.

    W = net.GaussianFill([], ["W"], mean=0.0, std=1.0, shape=[5, 3], run_once=0)
    b = net.ConstantFill([], ["b"], shape=[5,], value=1.0, run_once=0)
    

    现在一个简单的代码:Note由于BlonReference对象知道它由什么网络创建的,所以除了从net中创建op,你还可以通过BlobReference创建op。因此,我们可以通过如下方式创建FC操作。

    Y = X.FC([W, b], ["Y"])
    

    事实上,在底下,X.FC(...)只是简单的委托net.FC来实现,X.FC()会将X作为op的第一个输入。所以上面的操作其实等价于下面的:

    Y = net.FC([X, W, b], ["Y"])
    

    现在让我们看下当前这个网络。

    print("Current network proto:\n\n{}".format(net.Proto()))
    
    Current network proto:
    name: "my_first_net"
    op {
      output: "X"
      name: ""
      type: "GaussianFill"
      arg {
        name: "std"
        f: 1.0
      }
      arg {
        name: "run_once"
        i: 0
      }
      arg {
        name: "shape"
        ints: 2
        ints: 3
      }
      arg {
        name: "mean"
        f: 0.0
      }
    }
    op {
      output: "W"
      name: ""
      type: "GaussianFill"
      arg {
        name: "std"
        f: 1.0
      }
      arg {
        name: "run_once"
        i: 0
      }
      arg {
        name: "shape"
        ints: 5
        ints: 3
      }
      arg {
        name: "mean"
        f: 0.0
      }
    }
    op {
      output: "b"
      name: ""
      type: "ConstantFill"
      arg {
        name: "run_once"
        i: 0
      }
      arg {
        name: "shape"
        ints: 5
      }
      arg {
        name: "value"
        f: 1.0
      }
    }
    op {
      input: "X"
      input: "W"
      input: "b"
      output: "Y"
      name: ""
      type: "FC"
    }
    

    是不是觉得太过冗长?GOOD~让我们尝试下把它变成一个图。用ipython显示。

    from caffe2.python import net_drawer
    from IPython import display
    graph = net_drawer.GetPydotGraph(net, rankdir="LR")
    display.Image(graph.create_png(), width=800)
    
    image.png
    目前为止,我们已经定义了一个Net,但是并没有执行任何东西。记住,上面的net只是一个protobuf,仅仅定义了网路的结构。当我们真正跑起这个网络时,底层发生的事件如下。
    - 实例化protobuf中定义的C++net 对象
    - 调用实例化后的net的Run()函数
    在我们进行任何操作前,我们应该先使用ResetWorkspace()清空workspace里的东
    西。
    NOTE有两种方式通过python来跑一个网络。我们选择第一种来展示。
    1. 使用 workspace.RunNetOnce()
    2. 第二种更复杂点:需要两步,a) 调用workspace.CreateNet()创建C++net对象,b)使用workspace.RunNet(),这步需要传递网络的名字作为参数。

    第一种

    workspace.ResetWorkspace()
    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    workspace.RunNetOnce(net)
    print("Blobs in the workspace after execution: {}".format(workspace.Blobs()))
    # Let's dump the contents of the blobs
    for name in workspace.Blobs():
        print("{}:\n{}".format(name, workspace.FetchBlob(name)))
    

    输出如下:

    Current blobs in the workspace: []
    Blobs in the workspace after execution: ['W', 'X', 'Y', 'b']
    W:
    [[-0.96537346  0.42591459  0.66788739]
     [-0.47695673  2.25724339 -0.10370601]
     [-0.20327474 -3.07469416  0.47715324]
     [-1.62159526  0.73711687 -1.42365313]
     [ 0.60718107 -0.50448036 -1.17132831]]
    X:
    [[-0.99601173 -0.61438894  0.10042733]
     [ 0.23359862  0.15135486  0.77555442]]
    Y:
    [[ 1.76692021  0.07781416  3.13944149  2.01927781  0.58755434]
     [ 1.35693741  1.14979863  0.85720366 -0.37135673  0.15705228]]
    b:
    [ 1.  1.  1.  1.  1.]
    

    第二种
    现在尝试第二种方法去创建这个网络,并跑起它。

    workspace.ResetWorkspace()
    print("Current blobs in the workspace: {}".format(workspace.Blobs()))
    workspace.CreateNet(net)
    workspace.RunNet(net.Proto().name)#传入名字
    print("Blobs in the workspace after execution: {}".format(workspace.Blobs()))
    for name in workspace.Blobs():
        print("{}:\n{}".format(name, workspace.FetchBlob(name)))
    

    输出

    Current blobs in the workspace: []
    Blobs in the workspace after execution: ['W', 'X', 'Y', 'b']
    W:
    [[-0.29295802  0.02897477 -1.25667715]
     [-1.82299471  0.92877913  0.33613944]
     [-0.64382178 -0.68545657 -0.44015241]
     [ 1.10232282  1.38060772 -2.29121733]
     [-0.55766547  1.97437167  0.39324901]]
    X:
    [[-0.47522315 -0.40166432  0.7179445 ]
     [-0.8363331  -0.82451206  1.54286408]]
    Y:
    [[ 0.22535783  1.73460138  1.2652775  -1.72335696  0.7543118 ]
     [-0.71776152  2.27745867  1.42452145 -4.59527397  0.4452306 ]]
    b:
    [ 1.  1.  1.  1.  1.]
    

    RunNetOnce()RunNet()之间有不少差异,其中最大的差异就是计算耗时。因为RunNetOnce()涉及到protobuf的序列化,和实例化网络。这可能会使用很长时间。让我们来看下开销。

    # It seems that %timeit magic does not work well with
    # C++ extensions so we'll basically do for loops
    start = time.time()
    for i in range(1000):
        workspace.RunNetOnce(net)
    end = time.time()
    print('Run time per RunNetOnce: {}'.format((end - start) / 1000))
    
    start = time.time()
    for i in range(1000):
        workspace.RunNet(net.Proto().name)
    end = time.time()
    print('Run time per RunNet: {}'.format((end - start) / 1000))
    

    输出如下:

    Run time per RunNetOnce: 0.000364284992218
    Run time per RunNet: 4.42600250244e-06
    

    可以看到RunNet()更快。

    结语:以上就是Caffe2的Python接口的一些主要部件。装载请注明出处:
    http://www.jianshu.com/c/cf07b31bb5f2

    相关文章

      网友评论

        本文标题:Caffe2 的基本数据结构(Basics of Caffe2

        本文链接:https://www.haomeiwen.com/subject/iknctxtx.html