caffe2

本文轉(zhuǎn)載http://www.reibang.com/p/5c0fd1c9fef9?from=timeline

學(xué)習(xí)思路

1铃肯、先看官方文檔患亿,學(xué)習(xí)如何使用python調(diào)用caffe2包,包括

  • Basics of Caffe2 - Workspaces, Operators, and Nets
  • Toy Regression
  • Image Pre-Processing
  • Loading Pre-Trained Models
  • MNIST - Create a CNN from Scratch

caffe2官方教程以python語(yǔ)言為主押逼,指導(dǎo)如何使用python調(diào)用caffe2步藕,文檔依次從最基本caffe中的幾個(gè)重要的類的概念、如何使用基礎(chǔ)類搭建一個(gè)小網(wǎng)絡(luò)挑格、如何數(shù)據(jù)預(yù)處理咙冗、如何使用預(yù)訓(xùn)練的模型、如何構(gòu)造復(fù)雜的網(wǎng)絡(luò)來(lái)講述caffe2的使用漂彤。初學(xué)者可以先行學(xué)習(xí)官方文檔caffe2-tutorials雾消,理解caffe2 中的網(wǎng)絡(luò)構(gòu)建、網(wǎng)絡(luò)訓(xùn)練的理念與思路挫望,體會(huì)caffe2與caffe在整體構(gòu)造上的不同立润。

2、結(jié)合著caffe2源碼看python實(shí)際調(diào)用的c++類

在python中媳板,caffe2這個(gè)包中類與函數(shù)大部分是封裝了源碼文件夾caffe2/caffe2/core下的c++源文件桑腮,如基礎(chǔ)數(shù)據(jù)類Tensor,操作類Operator等蛉幸,通過(guò)使用python中類的使用破讨,找到對(duì)應(yīng)c++源碼中類和函數(shù)的構(gòu)造和實(shí)現(xiàn),可以為使用c++直接構(gòu)建和訓(xùn)練網(wǎng)絡(luò)打下準(zhǔn)備奕纫。

以下總結(jié)基于官方文檔和部分網(wǎng)絡(luò)資料提陶。

基礎(chǔ)知識(shí)

首先從我們自己的角度出發(fā)來(lái)思考,假設(shè)我們自己需要寫一個(gè)簡(jiǎn)單的多層神經(jīng)網(wǎng)絡(luò)并訓(xùn)練匹层,一般邏輯上我們需要考慮數(shù)據(jù)的定義隙笆、數(shù)據(jù)的流動(dòng) 、數(shù)據(jù)的更新。

  • 數(shù)據(jù)如何定義:訓(xùn)練數(shù)據(jù)和網(wǎng)絡(luò)參數(shù)以什么形式存儲(chǔ)
  • 數(shù)據(jù)如何流動(dòng):訓(xùn)練數(shù)據(jù)經(jīng)過(guò)哪些運(yùn)算得到輸出撑柔,其實(shí)就是網(wǎng)絡(luò)的定義
  • 數(shù)據(jù)如何更新:使用什么樣的梯度更新方法與參數(shù)煤率,其實(shí)就是如何訓(xùn)練

在caffe中,數(shù)據(jù)儲(chǔ)存在Blob類的實(shí)例當(dāng)中乏冀,在這里蝶糯,我們可以理解blob就像是numpy中數(shù)組,起的作用就是存儲(chǔ)數(shù)據(jù)辆沦。輸入的blobs經(jīng)過(guò)不同層的往前傳遞昼捍,得到輸出的blobs,caffe中肢扯,我們可以認(rèn)為對(duì)數(shù)據(jù)最基本的運(yùn)算單位是layer妒茬。每一層的layer定義了不同的計(jì)算方式,數(shù)據(jù)經(jīng)過(guò)不同的層蔚晨,都做了相應(yīng)的運(yùn)算乍钻,由這些layers組合到一起網(wǎng)絡(luò)即構(gòu)成了net,net本質(zhì)上是一個(gè)計(jì)算網(wǎng)絡(luò)铭腕。當(dāng)數(shù)據(jù)流動(dòng)的方式構(gòu)建好了银择,反向傳遞的梯度計(jì)算的方式也確定,在這個(gè)基礎(chǔ)之上累舷,caffe中使用solver類來(lái)給定梯度更新的規(guī)則浩考,網(wǎng)絡(luò)在solver的控制下,不斷讓數(shù)據(jù)前傳被盈,再反傳求梯度析孽,再使用梯度更新權(quán)值,循環(huán)往復(fù)只怎。

所以對(duì)應(yīng)著caffe中袜瞬,基礎(chǔ)組成有四類:

  • blob:存儲(chǔ)數(shù)據(jù)和權(quán)值
  • layer:輸入數(shù)據(jù)blob 形式,輸出數(shù)據(jù)blob形式身堡,層定義了計(jì)算
  • net:由多個(gè)layers組成邓尤,構(gòu)成整體的網(wǎng)絡(luò)
  • solver:定義了訓(xùn)練規(guī)則

再看caffe2中:

在caffe2中,operator是caffe2中的特色盾沫,取代了caffe中l(wèi)ayer作為net的基本構(gòu)造單位裁赠。如下圖所示殿漠,我們可以使用一個(gè)InnerProduct操作運(yùn)輸符號(hào)來(lái)完成InnerProductLayer的功能赴精。operator的接口定義在caffe2/proto/caffe2.proto,一般來(lái)說(shuō)绞幌,operator接受一串輸入蕾哟,產(chǎn)生一串輸出。

[圖片上傳失敗...(image-a846bb-1529840965921)]

由于operator定義很基礎(chǔ),很抽象谭确,因此caffe2中的權(quán)值初始化帘营、前傳、反傳逐哈、梯度更新都可以用operator實(shí)現(xiàn)芬迄,所以solver、layer類在caffe2中都不是必要的昂秃。在caffe2中禀梳,對(duì)應(yīng)的基礎(chǔ)組成有

  • blob:存儲(chǔ)數(shù)據(jù)
  • operator:輸入blob,輸出blob肠骆,定義了計(jì)算規(guī)則
  • 網(wǎng)絡(luò):net算途,由多個(gè)operator組合實(shí)現(xiàn)
  • workspace:caffe中沒(méi)有,可以理解成變量的空間蚀腿,便于管理網(wǎng)絡(luò)和變量

具體使用和理解如下,先用python:

在使用之前嘴瓤,我們先導(dǎo)入caffe2.core和workspace,基礎(chǔ)的類和函數(shù)都在其中莉钙。同時(shí)我們需要導(dǎo)入caffe2.proto來(lái)對(duì)protobuf文件進(jìn)行必要操作廓脆。

# 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
# Let's show all plots inline.
%matplotlib inline

1、workspace

我們可以把workspace理解成matlab中變量存儲(chǔ)區(qū)磁玉,我們可以把定義好的數(shù)據(jù)blob或net放到都在一個(gè)workspace中狞贱,也可以用不用的workspace來(lái)區(qū)分齿穗。

下面我們打印一下當(dāng)前workspace中blob情況昨凡。Blobs()取出blob榆芦,HasBlobs(name)判斷是否有此名字的blob绞旅。

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

一開始重罪,當(dāng)然結(jié)果是啥也沒(méi)有宵溅。

我們使用FeedBlob來(lái)給當(dāng)前workspace添加blob项滑,再打印出來(lái):

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

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

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]]

當(dāng)然曙旭,我們也用多個(gè)名字定義多個(gè)workspace别垮,并且可以切換工作空間便监。我們可以使用currentworkspace()在訪問(wèn)當(dāng)前工作空間,使用switchworkspace(name)來(lái)切換工作空間碳想。

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

# Switch the workspace. The second argument "True" means creating
# the workspace if it is missing.
workspace.SwitchWorkspace("gutentag", True)

# Let's print the current workspace. Note that there is nothing in the
# workspace yet.
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: []

總結(jié)一下烧董,在這里workspace功能類似于matlab中的工作區(qū),變量存儲(chǔ)在其中胧奔,我們可以通過(guò)工作區(qū)去訪問(wèn)在工作區(qū)中net和blob逊移。

2、Operators

通常我們?cè)趐ython中龙填,可以使用core.CreateOperator來(lái)直接創(chuàng)造胳泉,也可以使用core.Net來(lái)訪問(wèn)創(chuàng)建operator,還可以使用modelHelper來(lái)訪問(wèn)創(chuàng)建operators拐叉。在這里我們使用core.CreateOperator來(lái)簡(jiǎn)單理解operator,在實(shí)際情況下扇商,我們創(chuàng)建網(wǎng)絡(luò)的時(shí)候凤瘦,不會(huì)直接創(chuàng)建每個(gè)operator,這樣太麻煩案铺,一般使用modelhelper來(lái)幫忙我們創(chuàng)建網(wǎng)絡(luò)蔬芥。

# Create an operator.
op = core.CreateOperator(
    "Relu", # The type of operator that we want to run
    ["X"], # A list of input blobs by their names
    ["Y"], # A list of output blobs by their names
)
# and we are done!

上面的代碼創(chuàng)建了一個(gè)Relu運(yùn)算符,在這里需要知道控汉,在python中創(chuàng)建一個(gè)operator坝茎,只是定義了一個(gè)operator,其實(shí)并沒(méi)有運(yùn)行這個(gè)operator暇番。在上面代碼中創(chuàng)建的op嗤放,實(shí)際上是一個(gè)protobuf對(duì)象。

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"

在創(chuàng)造op之后壁酬,我們?cè)诋?dāng)前的工作區(qū)中添加輸入X次酌,然后使用RunOperatorOnce運(yùn)行這個(gè)operator。運(yùn)行之后舆乔,我們對(duì)比下得到的結(jié)果岳服。

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

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)))

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.        ]]

此外,operator相對(duì)于layer更為抽象希俩。operator不僅僅可以替代layer類吊宋,還可以接受無(wú)參數(shù)的輸入來(lái)輸出數(shù)據(jù),從而用來(lái)生成數(shù)據(jù)颜武,常用來(lái)初始化權(quán)值璃搜。下面這一段就可以用來(lái)初始化權(quán)值。

op = core.CreateOperator(
    "GaussianFill",
    [], # GaussianFill does not need any parameters.
    ["W"],
    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: "W"
name: ""
type: "GaussianFill"
arg {
  name: "std"
  f: 1.0
}
arg {
  name: "shape"
  ints: 100
  ints: 100
}
arg {
  name: "mean"
  f: 1.0
}

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

[圖片上傳失敗...(image-e460b8-1529840965921)]

3鳞上、Nets

Nets是一系列operator的集合这吻,從本質(zhì)上,是由operator構(gòu)成的計(jì)算圖篙议。Caffe2中core.net 封裝了源碼中 NetDef 類唾糯。我們舉個(gè)栗子,創(chuàng)建網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)以下的公式鬼贱。

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

首先創(chuàng)建網(wǎng)絡(luò):

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

Current network proto:

name: "my_first_net"

首先使用生成權(quán)值和輸入移怯,在這里,使用core.net來(lái)訪問(wèn)創(chuàng)建:

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()))
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)

生成輸出:

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

我們打印下當(dāng)前的網(wǎng)絡(luò):

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"
}

在這里这难,我們可以畫出來(lái)定義的網(wǎng)絡(luò):

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-521a46-1529840965921)]

和operator類似舟误,在這里我們只定義了一個(gè)net,但是并沒(méi)有運(yùn)行net的計(jì)算雁佳。當(dāng)我們?cè)趐ython運(yùn)行網(wǎng)絡(luò)時(shí)脐帝,實(shí)際上在c++層面做了兩件事情:

  • 由protobuf定義初始化c++ 的net對(duì)象
  • 調(diào)用初始化了的net的run函數(shù)

在python中有兩種方法來(lái)運(yùn)行一個(gè)net:

  • 方法1:使用workspace.RunNetOnce,初始化網(wǎng)絡(luò),運(yùn)行網(wǎng)絡(luò)糖权,然后銷毀網(wǎng)絡(luò)堵腹。
  • 方法2:先使用workspace.CreateNet初始化網(wǎng)絡(luò),然后使用workspace.RunNet來(lái)運(yùn)行網(wǎng)絡(luò)

方法一:

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.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.]

方法二:

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.]

在這里星澳,大家可能比較疑惑為什么會(huì)有兩種運(yùn)行網(wǎng)絡(luò)的方式疚顷,在之后的實(shí)際應(yīng)用中,大家就會(huì)慢慢理解禁偎,在這里腿堤,暫時(shí)記住有這樣兩種運(yùn)行網(wǎng)絡(luò)的方式即可。

總結(jié)一下如暖,在caffe2中

  • workspace是工作空間笆檀,在worspace中,可以存儲(chǔ)網(wǎng)絡(luò)結(jié)構(gòu)類Net和數(shù)據(jù)存儲(chǔ)類Blob.
  • 輸入數(shù)據(jù)盒至、權(quán)值酗洒、輸出數(shù)據(jù)都存儲(chǔ)在Blob中
  • Operator類用來(lái)定義來(lái)數(shù)據(jù)如何計(jì)算,由多個(gè)operators構(gòu)成Net枷遂,operator的作用強(qiáng)大
  • Net類是由operator構(gòu)成的整體樱衷。

應(yīng)用舉例

在基礎(chǔ)知識(shí)中,我們理解了workspace酒唉,operator矩桂,net等基本的概念,在這里我們結(jié)合caffe2的官方文檔簡(jiǎn)單舉出幾個(gè)例子痪伦。

栗子1-回歸的小栗子

第一個(gè)栗子幫助大家理解caffe2框架網(wǎng)絡(luò)構(gòu)建侄榴、參數(shù)初始化、訓(xùn)練网沾、圖等的一些關(guān)于整體框架的理念牲蜀。

假設(shè)我們要做訓(xùn)練一個(gè)簡(jiǎn)單的網(wǎng)絡(luò),擬合下面這樣的一個(gè)回歸函數(shù):

y = wx + b
其中:w=[2.0, 1.5]  b=0.5

一般訓(xùn)練數(shù)據(jù)是從外部讀進(jìn)來(lái)绅这,在這里訓(xùn)練數(shù)據(jù)我們直接用caffe2中的operator生成涣达,我們?cè)诤竺娴睦踝又杏袝?huì)舉例說(shuō)明如何從外部讀入數(shù)據(jù)。

首先導(dǎo)入必要的包:

from caffe2.python import core, cnn, net_drawer, workspace, visualize
import numpy as np
from IPython import display
from matplotlib import pyplot

在這里证薇,首先我們需要建立兩個(gè)網(wǎng)絡(luò)圖:

  • 一個(gè)用來(lái)生成訓(xùn)練數(shù)據(jù)度苔、初始化權(quán)值的網(wǎng)絡(luò)圖
  • 一個(gè)用來(lái)用來(lái)訓(xùn)練,更新剃度的網(wǎng)絡(luò)圖

這里caffe2的思路和caffe不太一樣浑度,在caffe中寇窑,我們?cè)谟?xùn)練網(wǎng)絡(luò)中定義好了參數(shù)的初始化方式,網(wǎng)絡(luò)加載時(shí)箩张,程序會(huì)根據(jù)網(wǎng)絡(luò)定義甩骏,自動(dòng)初始化權(quán)值窗市,我們只需要對(duì)這個(gè)網(wǎng)絡(luò),使用solver不斷的前傳和反傳饮笛,更新參數(shù)即可咨察。在caffe2中,我們要把所有網(wǎng)絡(luò)的搭建福青、初始化摄狱、梯度生成、梯度更新都使用operator這樣一個(gè)方式來(lái)實(shí)現(xiàn)无午,所有的數(shù)據(jù)的生成媒役、流動(dòng)都要在圖中反映出來(lái)。這樣宪迟,那么初始化這一部分我就需要一些operators來(lái)實(shí)現(xiàn)酣衷,這些operators組成的net,我們把它單獨(dú)拿出來(lái)次泽,稱它為用于初始化的網(wǎng)絡(luò)鸥诽。我們可以結(jié)合著代碼來(lái)理解。

首先箕憾,我們創(chuàng)建一個(gè)生成訓(xùn)練數(shù)據(jù)和初始化權(quán)值的網(wǎng)絡(luò)牡借。

init_net = core.Net("init")
# The ground truth parameters.
W_gt = init_net.GivenTensorFill(
    [], "W_gt", shape=[1, 2], values=[2.0, 1.5])
B_gt = init_net.GivenTensorFill([], "B_gt", shape=[1], values=[0.5])
# Constant value ONE is used in weighted sum when updating parameters.
ONE = init_net.ConstantFill([], "ONE", shape=[1], value=1.)
# ITER is the iterator count.
ITER = init_net.ConstantFill([], "ITER", shape=[1], value=0, dtype=core.DataType.INT32)

# For the parameters to be learned: we randomly initialize weight
# from [-1, 1] and init bias with 0.0.
W = init_net.UniformFill([], "W", shape=[1, 2], min=-1., max=1.)
B = init_net.ConstantFill([], "B", shape=[1], value=0.0)
print('Created init net.')

接下來(lái),我們定義一個(gè)用來(lái)訓(xùn)練的網(wǎng)絡(luò)袭异。

train_net = core.Net("train")
# First, we generate random samples of X and create the ground truth.
X = train_net.GaussianFill([], "X", shape=[64, 2], mean=0.0, std=1.0, run_once=0)
Y_gt = X.FC([W_gt, B_gt], "Y_gt")
# We add Gaussian noise to the ground truth
noise = train_net.GaussianFill([], "noise", shape=[64, 1], mean=0.0, std=1.0, run_once=0)
Y_noise = Y_gt.Add(noise, "Y_noise")
# Note that we do not need to propagate the gradients back through Y_noise,
# so we mark StopGradient to notify the auto differentiating algorithm
# to ignore this path.
Y_noise = Y_noise.StopGradient([], "Y_noise")

# Now, for the normal linear regression prediction, this is all we need.
Y_pred = X.FC([W, B], "Y_pred")

# The loss function is computed by a squared L2 distance, and then averaged
# over all items in the minibatch.
dist = train_net.SquaredL2Distance([Y_noise, Y_pred], "dist")
loss = dist.AveragedLoss([], ["loss"])

我們來(lái)畫出我們定義的訓(xùn)練網(wǎng)絡(luò)的圖:

graph = net_drawer.GetPydotGraph(train_net.Proto().op, "train", rankdir="LR")
display.Image(graph.create_png(), width=800)

[圖片上傳失敗...(image-d38cb2-1529840965921)]

在這里钠龙,通過(guò)上面的圖,我們可以看到init_net部分生成了訓(xùn)練數(shù)據(jù)御铃、初始化的權(quán)值W碴里,以及用來(lái)生成計(jì)算過(guò)程中需要的常數(shù)矩陣,而train_net構(gòu)建了前向計(jì)算過(guò)程上真。

但是我們還沒(méi)有定義如何反向傳導(dǎo)咬腋,和很多其他的深度學(xué)習(xí)框架類似,caffe2支持自動(dòng)梯度推導(dǎo)睡互,自動(dòng)生成產(chǎn)生梯度的operator根竿。

接下來(lái),我們給train_net加上梯度運(yùn)算:

# Get gradients for all the computations above.
gradient_map = train_net.AddGradientOperators([loss])
graph = net_drawer.GetPydotGraph(train_net.Proto().op, "train", rankdir="LR")
display.Image(graph.create_png(), width=800)

[圖片上傳失敗...(image-f00208-1529840965921)]

可以看到就珠,網(wǎng)絡(luò)后半部分進(jìn)行了求梯度運(yùn)算寇壳,輸出了各學(xué)習(xí)參數(shù)的梯度值,當(dāng)我們得到這些梯度值后妻怎,我們?cè)佾@得當(dāng)前訓(xùn)練的學(xué)習(xí)率壳炎,我們就可以使用梯度下降方法更新參數(shù)。

接下來(lái)逼侦,我們?cè)趖rain_net加上SGD更新的部分:

# Increment the iteration by one.
train_net.Iter(ITER, ITER)
# Compute the learning rate that corresponds to the iteration.
LR = train_net.LearningRate(ITER, "LR", base_lr=-0.1,
                            policy="step", stepsize=20, gamma=0.9)

# Weighted sum
train_net.WeightedSum([W, ONE, gradient_map[W], LR], W)
train_net.WeightedSum([B, ONE, gradient_map[B], LR], B)

# Let's show the graph again.
graph = net_drawer.GetPydotGraph(train_net.Proto().op, "train", rankdir="LR")
display.Image(graph.create_png(), width=800)

[圖片上傳失敗...(image-6c0e4-1529840965921)]

到這里匿辩,整個(gè)模型的參數(shù)初始化腰耙、前傳、反傳铲球、梯度更新全都使用operator定義好了挺庞。這個(gè)就是caffe2中使用operator的威力,它使得caffe2較caffe具有不可比擬的靈活性睬辐。在這里注意挠阁,我們只是定義了網(wǎng)絡(luò)宾肺,還沒(méi)有運(yùn)行網(wǎng)絡(luò)溯饵,下面讓我們來(lái)運(yùn)行它們:

workspace.RunNetOnce(init_net)
workspace.CreateNet(train_net)
print("Before training, W is: {}".format(workspace.FetchBlob("W")))
print("Before training, B is: {}".format(workspace.FetchBlob("B")))

True
Before training, W is: [[-0.77634162 -0.88467366]]
Before training, B is: [ 0.]

#run the train net 100 times 
for i in range(100):
    workspace.RunNet(train_net.Proto().name)

print("After training, W is: {}".format(workspace.FetchBlob("W")))
print("After training, B is: {}".format(workspace.FetchBlob("B")))
print("Ground truth W is: {}".format(workspace.FetchBlob("W_gt")))
print("Ground truth B is: {}".format(workspace.FetchBlob("B_gt")))

在這里,我們需要注意一點(diǎn)锨用,我們使用了RunNetOnce和RunNet兩種不同的方式來(lái)運(yùn)行網(wǎng)絡(luò)丰刊,還記得兩種運(yùn)行網(wǎng)絡(luò)的方式么?

  • 方法1:使用workspace.RunNetOnce,這個(gè)函數(shù)會(huì)初始化網(wǎng)絡(luò)增拥,運(yùn)行網(wǎng)絡(luò)啄巧,然后銷毀網(wǎng)絡(luò)。
  • 方法2:先使用workspace.CreateNet初始化網(wǎng)絡(luò)掌栅,然后使用workspace.RunNet來(lái)運(yùn)行網(wǎng)絡(luò)

一開始我也不明白為什么要有兩種方式運(yùn)行網(wǎng)絡(luò)秩仆,現(xiàn)在結(jié)合init_net和train_net來(lái)看,就非常明白了猾封。RunNetOnce用來(lái)運(yùn)行生成權(quán)值和數(shù)據(jù)的網(wǎng)絡(luò)澄耍,常用于初始化,這樣的網(wǎng)絡(luò)一次生成完晌缘,權(quán)值輸出或數(shù)據(jù)就存在當(dāng)前的workspace中齐莲,網(wǎng)絡(luò)本身就沒(méi)有存在的必要了,就直接銷毀磷箕,而RunNet可以用來(lái)重復(fù)訓(xùn)練網(wǎng)絡(luò)选酗,一開始使用CreateNet,不斷迭代調(diào)用RunNet就可以不斷運(yùn)行網(wǎng)絡(luò)更新參數(shù)了岳枷。

以下是訓(xùn)練結(jié)果:

After training, W is: [[ 1.95769441  1.47348857]]
After training, B is: [ 0.45236012]
Ground truth W is: [[ 2.   1.5]]
Ground truth B is: [ 0.5]

芒填,總結(jié)一下:

  • caffe2中使用operator完成初始化參數(shù)、前傳空繁、反傳氢烘、梯度更新
  • caffe2中一個(gè)模型通常包含一個(gè)初始化網(wǎng)絡(luò),一個(gè)訓(xùn)練網(wǎng)絡(luò)

最后家厌,還要說(shuō)明一點(diǎn)播玖,這個(gè)例子中,我們直接使用operator來(lái)構(gòu)建網(wǎng)絡(luò)饭于。對(duì)于常見(jiàn)的深度網(wǎng)絡(luò)蜀踏,直接用operator構(gòu)建會(huì)步驟會(huì)非常繁瑣维蒙,所以caffe2中為了簡(jiǎn)化網(wǎng)絡(luò)的搭建,又封裝了model_helper類來(lái)幫助我們方便地搭建網(wǎng)絡(luò)果覆,譬如對(duì)于卷積神經(jīng)網(wǎng)絡(luò)中的常見(jiàn)的層颅痊,我們就可以直接使用model_helper來(lái)構(gòu)建。在之后的栗子中也有說(shuō)明局待。

栗子二-圖像預(yù)處理

眾所周知斑响,網(wǎng)絡(luò)中訓(xùn)練需要做一系列的數(shù)據(jù)預(yù)處理,在這里钳榨,caffe和caffe2中處理的方式一樣舰罚。都需要經(jīng)過(guò)XXX等步。因?yàn)闆](méi)有什么區(qū)別薛耻,在這里就不舉了营罢,直接參考官方教程Image Pre-Processing,解釋非常清楚饼齿。給個(gè)贊饲漾。

栗子三-加載預(yù)訓(xùn)練模型

首先,我們使用一個(gè)caffe2中定義的下載模塊去下載一個(gè)預(yù)訓(xùn)練好的模型缕溉,命令行中輸入如下的命令會(huì)下載squeezenet這個(gè)預(yù)訓(xùn)練模型:

python -m caffe2.python.models.download -i squeezenet

當(dāng)下載完成時(shí)考传,在caffe2/python/model底下有一個(gè)squeezenet文件,文件夾底下有兩個(gè)文件init_net.pb,predict_net.pb分別保存了權(quán)值和網(wǎng)絡(luò)定義证鸥。

在python中我們使用caffe2的workspace來(lái)存放這個(gè)模型的網(wǎng)絡(luò)定義和權(quán)重僚楞,并且把它們加載到blob、init_net和predict_net敌土。我們需要使用一個(gè)workspace.Predictor來(lái)接收兩個(gè)protobuf镜硕,然后剩下的就可以交給caffe2了。

所以一般加載預(yù)測(cè)模型只需要幾步:

1返干、讀入protobuf文件

 with open("init_net.pb") as f:
     init_net = f.read()
 with open("predict_net.pb") as f:
     predict_net = f.read()      

2兴枯、使用workspace中的Predictor來(lái)加載從protobuf中取到的blobs:

 p = workspace.Predictor(init_net, predict_net)

3、運(yùn)行網(wǎng)絡(luò)矩欠,得到結(jié)果:

 results = p.run([img])

需要注意的這里的img是預(yù)處理過(guò)的圖像财剖。

以下是官方文檔下的一個(gè)完整的栗子:

首先配置一下問(wèn)文件路徑等,導(dǎo)入常用包:

# where you installed caffe2\. Probably '~/caffe2' or '~/src/caffe2'.
CAFFE2_ROOT = "~/caffe2"
# assumes being a subdirectory of caffe2
CAFFE_MODELS = "~/caffe2/caffe2/python/models"
# if you have a mean file, place it in the same dir as the model

%matplotlib inline
from caffe2.proto import caffe2_pb2
import numpy as np
import skimage.io
import skimage.transform
from matplotlib import pyplot
import os
from caffe2.python import core, workspace
import urllib2
print("Required modules imported.")

IMAGE_LOCATION =  "https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg"

# What model are we using? You should have already converted or downloaded one.
# format below is the model's:
# folder, INIT_NET, predict_net, mean, input image size
# you can switch the comments on MODEL to try out different model conversions
MODEL = 'squeezenet', 'init_net.pb', 'predict_net.pb', 'ilsvrc_2012_mean.npy', 227

# codes - these help decypher the output and source from a list from AlexNet's object codes to provide an result like "tabby cat" or "lemon" depending on what's in the picture you submit to the neural network.
# The list of output codes for the AlexNet models (also squeezenet)
codes =  "https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac071eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes"
print "Config set!"

定義數(shù)據(jù)預(yù)處理的函數(shù):

def crop_center(img,cropx,cropy):
    y,x,c = img.shape
    startx = x//2-(cropx//2)
    starty = y//2-(cropy//2)    
    return img[starty:starty+cropy,startx:startx+cropx]

def rescale(img, input_height, input_width):
    print("Original image shape:" + str(img.shape) + " and remember it should be in H, W, C!")
    print("Model's input shape is %dx%d") % (input_height, input_width)
    aspect = img.shape[1]/float(img.shape[0])
    print("Orginal aspect ratio: " + str(aspect))
    if(aspect>1):
        # landscape orientation - wide image
        res = int(aspect * input_height)
        imgScaled = skimage.transform.resize(img, (input_width, res))
    if(aspect<1):
        # portrait orientation - tall image
        res = int(input_width/aspect)
        imgScaled = skimage.transform.resize(img, (res, input_height))
    if(aspect == 1):
        imgScaled = skimage.transform.resize(img, (input_width, input_height))
    pyplot.figure()
    pyplot.imshow(imgScaled)
    pyplot.axis('on')
    pyplot.title('Rescaled image')
    print("New image shape:" + str(imgScaled.shape) + " in HWC")
    return imgScaled
print "Functions set."

# set paths and variables from model choice and prep image
CAFFE2_ROOT = os.path.expanduser(CAFFE2_ROOT)
CAFFE_MODELS = os.path.expanduser(CAFFE_MODELS)

# mean can be 128 or custom based on the model
# gives better results to remove the colors found in all of the training images
MEAN_FILE = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[3])
if not os.path.exists(MEAN_FILE):
    mean = 128
else:
    mean = np.load(MEAN_FILE).mean(1).mean(1)
    mean = mean[:, np.newaxis, np.newaxis]
print "mean was set to: ", mean

# some models were trained with different image sizes, this helps you calibrate your image
INPUT_IMAGE_SIZE = MODEL[4]

# make sure all of the files are around...
if not os.path.exists(CAFFE2_ROOT):
    print("Houston, you may have a problem.")
INIT_NET = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[1])
print 'INIT_NET = ', INIT_NET
PREDICT_NET = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[2])
print 'PREDICT_NET = ', PREDICT_NET
if not os.path.exists(INIT_NET):
    print(INIT_NET + " not found!")
else:
    print "Found ", INIT_NET, "...Now looking for", PREDICT_NET
    if not os.path.exists(PREDICT_NET):
        print "Caffe model file, " + PREDICT_NET + " was not found!"
    else:
        print "All needed files found! Loading the model in the next block."

# load and transform image
img = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)
img = rescale(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
img = crop_center(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
print "After crop: " , img.shape
pyplot.figure()
pyplot.imshow(img)
pyplot.axis('on')
pyplot.title('Cropped')

# switch to CHW
img = img.swapaxes(1, 2).swapaxes(0, 1)
pyplot.figure()
for i in range(3):
    # For some reason, pyplot subplot follows Matlab's indexing
    # convention (starting with 1). Well, we'll just follow it...
    pyplot.subplot(1, 3, i+1)
    pyplot.imshow(img[i])
    pyplot.axis('off')
    pyplot.title('RGB channel %d' % (i+1))

# switch to BGR
img = img[(2, 1, 0), :, :]

# remove mean for better results
img = img * 255 - mean

# add batch size
img = img[np.newaxis, :, :, :].astype(np.float32)
print "NCHW: ", img.shape

運(yùn)行一下癌淮,輸出結(jié)果:

Functions set.
mean was set to:  128
INIT_NET =  /home/aaron/models/squeezenet/init_net.pb
PREDICT_NET =  /home/aaron/models/squeezenet/predict_net.pb
Found  /home/aaron/models/squeezenet/init_net.pb ...Now looking for /home/aaron/models/squeezenet/predict_net.pb
All needed files found! Loading the model in the next block.
Original image shape:(751, 1280, 3) and remember it should be in H, W, C!
Model's input shape is 227x227
Orginal aspect ratio: 1.70439414115
New image shape:(227, 386, 3) in HWC
After crop:  (227, 227, 3)
NCHW:  (1, 3, 227, 227)

[圖片上傳失敗...(image-dac21d-1529840965921)]

[圖片上傳失敗...(image-178926-1529840965921)]

[圖片上傳失敗...(image-d38d08-1529840965921)]

當(dāng)圖像經(jīng)過(guò)處理之后躺坟,就可以按照前面的安排加載和運(yùn)行網(wǎng)絡(luò)。

# initialize the neural net

with open(INIT_NET) as f:
    init_net = f.read()
with open(PREDICT_NET) as f:
    predict_net = f.read()

p = workspace.Predictor(init_net, predict_net)

# run the net and return prediction
results = p.run([img])

# turn it into something we can play with and examine which is in a multi-dimensional array
results = np.asarray(results)
print "results shape: ", results.shape

results shape:  (1, 1, 1000, 1, 1)

這里輸出來(lái)了1000個(gè)值乳蓄,表示這張圖片分別對(duì)應(yīng)1000類的概率咪橙。我們可以取出來(lái)其中概率最高的值,來(lái)找到它對(duì)應(yīng)的標(biāo)簽:

# the rest of this is digging through the results

results = np.delete(results, 1)
index = 0
highest = 0
arr = np.empty((0,2), dtype=object)
arr[:,0] = int(10)
arr[:,1:] = float(10)
for i, r in enumerate(results):
    # imagenet index begins with 1!
    i=i+1
    arr = np.append(arr, np.array([[i,r]]), axis=0)
    if (r > highest):
        highest = r
        index = i

print index, " :: ", highest

# lookup the code and return the result
# top 3 results
# sorted(arr, key=lambda x: x[1], reverse=True)[:3]

# now we can grab the code list
response = urllib2.urlopen(codes)

# and lookup our result from the list
for line in response:
    code, result = line.partition(":")[::2]
    if (code.strip() == str(index)):
        print result.strip()[1:-2]

985  ::  0.979059
daisy

栗子四-創(chuàng)建一個(gè)CNN模型

1、模型美侦、幫助函數(shù)产舞、brew

在前面我們已經(jīng)基本介紹了在python中關(guān)于caffe2中基本的操作。

這個(gè)例子中菠剩,我們來(lái)簡(jiǎn)單搭建一個(gè)CNN模型易猫。在這個(gè)需要說(shuō)明一點(diǎn):

  • 在caffe中,我們通常說(shuō)一個(gè)模型具壮,其實(shí)就是一個(gè)網(wǎng)絡(luò)准颓,一個(gè)Net
  • 而在caffe2中,我們通常使用modelHelper來(lái)代表一個(gè)model棺妓,而這個(gè)model包含多個(gè)Net攘已,就像我們前面看到的,我們會(huì)使用一個(gè)初始化網(wǎng)絡(luò)init_net涧郊,還有有一個(gè)訓(xùn)練網(wǎng)絡(luò)net贯被,這兩個(gè)網(wǎng)絡(luò)圖都是model的一部分眼五。

這一點(diǎn)需要大家區(qū)分開妆艘,不然容易疑惑。舉例看幼,如果我們要構(gòu)造一個(gè)模型批旺,只有一個(gè)FC層,在這里使用modelHelper來(lái)表示一個(gè)model诵姜,使用operators來(lái)構(gòu)造網(wǎng)絡(luò)汽煮,一般model有一個(gè)param_init_net和一個(gè)net。分別用于模型初始化和訓(xùn)練:

model = model_helper.ModelHelper(name="train")
# initialize your weight
weight = model.param_init_net.XavierFill(
    [],
    blob_out + '_w',
    shape=[dim_out, dim_in],
    **kwargs, # maybe indicating weight should be on GPU here
)
# initialize your bias
bias = model.param_init_net.ConstantFill(
    [],
    blob_out + '_b',
    shape=[dim_out, ],
    **kwargs,
)
# finally building FC
model.net.FC([blob_in, weights, bias], blob_out, **kwargs)

前面棚唆,我們說(shuō)過(guò)在日常搭建網(wǎng)絡(luò)的時(shí)候呢暇赤,我們通常不是完全使用operator搭建網(wǎng)絡(luò),因?yàn)槭褂眠@種方式宵凌,每個(gè)參數(shù)都需要我們手動(dòng)初始化鞋囊,以及每個(gè)operator都需要構(gòu)造,太過(guò)于繁瑣瞎惫。我們想著溜腐,對(duì)于常用層,能不能把構(gòu)造它的operators都封裝起來(lái)瓜喇,封裝成一個(gè)函數(shù)挺益,我們構(gòu)造時(shí)只需給這個(gè)函數(shù)要提供必要的參數(shù),函數(shù)中的代碼就能幫助我們完成層初始化和operator的構(gòu)建乘寒。

在caffe2中望众,為了便于開發(fā)者搭建網(wǎng)絡(luò),caffe2在python/helpers中提供了許多help函數(shù),像上面例子中的FC層烂翰,使用python/helpers/fc.py來(lái)構(gòu)造叉袍,非常簡(jiǎn)單就一行代碼:

fcLayer = fc(model, blob_in, blob_out, **kwargs) # returns a blob reference

這里面help函數(shù)能夠幫助我們將權(quán)值初始化和計(jì)算網(wǎng)絡(luò)自動(dòng)分開到兩個(gè)網(wǎng)絡(luò),這樣一來(lái)就簡(jiǎn)單多了刽酱。caffe2為了更方便調(diào)用和管理喳逛,把這些幫助函數(shù)集合到一起,放在brew這個(gè)包里面棵里∪笪模可以通過(guò)導(dǎo)入brew這個(gè)包來(lái)調(diào)用這些幫助函數(shù)。像上面的fc層的實(shí)現(xiàn)就可以使用:

from caffe2.python import brew
brew.fc(model, blob_in, blob_out, ...)

我們使用brew構(gòu)造網(wǎng)絡(luò)就十分簡(jiǎn)單殿怜,下面的代碼就構(gòu)造了一個(gè)LeNet模型:

from caffe2.python import brew

def AddLeNetModel(model, data):
    conv1 = brew.conv(model, data, 'conv1', 1, 20, 5)
    pool1 = brew.max_pool(model, conv1, 'pool1', kernel=2, stride=2)
    conv2 = brew.conv(model, pool1, 'conv2', 20, 50, 5)
    pool2 = brew.max_pool(model, conv2, 'pool2', kernel=2, stride=2)
    fc3 = brew.fc(model, pool2, 'fc3', 50 * 4 * 4, 500)
    fc3 = brew.relu(model, fc3, fc3)
    pred = brew.fc(model, fc3, 'pred', 500, 10)
    softmax = brew.softmax(model, pred, 'softmax')

caffe2 使用brew提供很多構(gòu)造網(wǎng)絡(luò)的幫助函數(shù)典蝌,大大簡(jiǎn)化了我們構(gòu)建網(wǎng)絡(luò)的過(guò)程。但實(shí)際上头谜,這些只是封裝的結(jié)果骏掀,網(wǎng)絡(luò)構(gòu)造的原理和之前說(shuō)的使用operators構(gòu)建的原理是一樣的。

2柱告、創(chuàng)建一個(gè)CNN模型用于MNIST手寫體數(shù)據(jù)集

首先截驮,導(dǎo)入必要的包:

%matplotlib inline
from matplotlib import pyplot
import numpy as np
import os
import shutil

from caffe2.python import core, model_helper, net_drawer, workspace, visualize, brew

# If you would like to see some really detailed initializations,
# you can change --caffe2_log_level=0 to --caffe2_log_level=-1
core.GlobalInit(['caffe2', '--caffe2_log_level=0'])
print("Necessities imported!")

下載MNIST dataset,并且把數(shù)據(jù)集轉(zhuǎn)成leveldb:

./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/train-images-idx3-ubyte --label_file ~/Downloads/train-labels-idx1-ubyte --output_file ~/caffe2_notebooks/tutorial_data/mnist/mnist-train-nchw-leveldb

./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/t10k-images-idx3-ubyte --label_file ~/Downloads/t10k-labels-idx1-ubyte --output_file ~/caffe2_notebooks/tutorial_data/mnist/mnist-test-nchw-leveldb

# This section preps your image and test set in a leveldb
current_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')

data_folder = os.path.join(current_folder, 'tutorial_data', 'mnist')
root_folder = os.path.join(current_folder, 'tutorial_files', 'tutorial_mnist')
image_file_train = os.path.join(data_folder, "train-images-idx3-ubyte")
label_file_train = os.path.join(data_folder, "train-labels-idx1-ubyte")
image_file_test = os.path.join(data_folder, "t10k-images-idx3-ubyte")
label_file_test = os.path.join(data_folder, "t10k-labels-idx1-ubyte")

# Get the dataset if it is missing
def DownloadDataset(url, path):
    import requests, zipfile, StringIO
    print "Downloading... ", url, " to ", path
    r = requests.get(url, stream=True)
    z = zipfile.ZipFile(StringIO.StringIO(r.content))
    z.extractall(path)

def GenerateDB(image, label, name):
    name = os.path.join(data_folder, name)
    print 'DB: ', name
    if not os.path.exists(name):
        syscall = "/usr/local/bin/make_mnist_db --channel_first --db leveldb --image_file " + image + " --label_file " + label + " --output_file " + name
        # print "Creating database with: ", syscall
        os.system(syscall)
    else:
        print "Database exists already. Delete the folder if you have issues/corrupted DB, then rerun this."
        if os.path.exists(os.path.join(name, "LOCK")):
            # print "Deleting the pre-existing lock file"
            os.remove(os.path.join(name, "LOCK"))

            if not os.path.exists(data_folder):
                os.makedirs(data_folder)
            if not os.path.exists(label_file_train):
                DownloadDataset("https://download.caffe2.ai/datasets/mnist/mnist.zip", data_folder)

            if os.path.exists(root_folder):
                print("Looks like you ran this before, so we need to cleanup those old files...")
                shutil.rmtree(root_folder)

            os.makedirs(root_folder)
            workspace.ResetWorkspace(root_folder)

            # (Re)generate the leveldb database (known to get corrupted...)
            GenerateDB(image_file_train, label_file_train, "mnist-train-nchw-leveldb")
            GenerateDB(image_file_test, label_file_test, "mnist-test-nchw-leveldb")

            print("training data folder:" + data_folder)
            print("workspace root folder:" + root_folder)

在這里,我們使用modelHelper來(lái)代表我們的模型际度,使用brew和operators來(lái)搭建模型葵袭,modelHelper包含了兩個(gè)net,包括param_init_net和net乖菱,分別代表初始化網(wǎng)絡(luò)和主訓(xùn)練網(wǎng)絡(luò)坡锡。

我們來(lái)一步一步分塊構(gòu)造模型:

(1)輸入部分(AddInput function)
(2)網(wǎng)絡(luò)計(jì)算部分(AddLeNetModel function)
(3)網(wǎng)絡(luò)訓(xùn)練部分,添加梯度運(yùn)算,更新等(AddTrainingOperators function)
(4)記錄統(tǒng)計(jì)部分窒所,打印一些統(tǒng)計(jì)數(shù)據(jù)來(lái)觀察(AddBookkeepingOperators function)

(1)輸入部分(AddInput function)

AddInput會(huì)從DB加載data鹉勒,AddInput加載完成之后,和得到data 和label:

- data with shape `(batch_size, num_channels, width, height)`
    - in this case `[batch_size, 1, 28, 28]` of data type *uint8*
- label with shape `[batch_size]` of data type *int*

def AddInput(model, batch_size, db, db_type):
    # load the data
    data_uint8, label = model.TensorProtosDBInput(
        [], ["data_uint8", "label"], batch_size=batch_size,
        db=db, db_type=db_type)
    # cast the data to float
    data = model.Cast(data_uint8, "data", to=core.DataType.FLOAT)
    # scale data from [0,255] down to [0,1]
    data = model.Scale(data, data, scale=float(1./256))
    # don't need the gradient for the backward pass
    data = model.StopGradient(data, data)
    return data, label

在這里簡(jiǎn)單解釋一下AddInput中的一些操作吵取,首先將data轉(zhuǎn)換成float類型禽额,這樣做是因?yàn)槲覀冎饕龈↑c(diǎn)運(yùn)算。為了保證計(jì)算穩(wěn)定海渊,我們將圖像從[0,255]縮放到[0,1]绵疲,并且這里做的事占位運(yùn)算,不需要保存未縮放之前的值臣疑。當(dāng)計(jì)算反向過(guò)程中盔憨,這一部分不需要計(jì)算梯度,我們使用StopGradient來(lái)禁止梯度反傳讯沈,這樣自動(dòng)生成梯度時(shí)郁岩,這個(gè)operator和它之前的operator就不會(huì)變了婿奔。

def AddInput(model, batch_size, db, db_type):
    # load the data
    data_uint8, label = model.TensorProtosDBInput(
        [], ["data_uint8", "label"], batch_size=batch_size,
        db=db, db_type=db_type)
    # cast the data to float
    data = model.Cast(data_uint8, "data", to=core.DataType.FLOAT)
    # scale data from [0,255] down to [0,1]
    data = model.Scale(data, data, scale=float(1./256))
    # don't need the gradient for the backward pass
    data = model.StopGradient(data, data)
    return data, label

在這個(gè)基礎(chǔ)上,就是加入網(wǎng)絡(luò)AddLenetModel问慎,同時(shí)加入一個(gè)AddAccuracy來(lái)追蹤模型的準(zhǔn)確率:

def AddLeNetModel(model, data):
    # Image size: 28 x 28 -> 24 x 24
    conv1 = brew.conv(model, data, 'conv1', dim_in=1, dim_out=20, kernel=5)
    # Image size: 24 x 24 -> 12 x 12
    pool1 = brew.max_pool(model, conv1, 'pool1', kernel=2, stride=2)
    # Image size: 12 x 12 -> 8 x 8
    conv2 = brew.conv(model, pool1, 'conv2', dim_in=20, dim_out=50, kernel=5)
    # Image size: 8 x 8 -> 4 x 4
    pool2 = brew.max_pool(model, conv2, 'pool2', kernel=2, stride=2)
    # 50 * 4 * 4 stands for dim_out from previous layer multiplied by the image size
    fc3 = brew.fc(model, pool2, 'fc3', dim_in=50 * 4 * 4, dim_out=500)
    fc3 = brew.relu(model, fc3, fc3)
    pred = brew.fc(model, fc3, 'pred', 500, 10)
    softmax = brew.softmax(model, pred, 'softmax')
    return softmax

def AddAccuracy(model, softmax, label):
    accuracy = model.Accuracy([softmax, label], "accuracy")
    return accuracy

接下來(lái)萍摊,我們將加入梯度生成和更新,這部分由AddTrainingOperators實(shí)現(xiàn)如叼,梯度生成和更新和之前例子中的原理一樣冰木。

def AddTrainingOperators(model, softmax, label):

# something very important happens here
xent = model.LabelCrossEntropy([softmax, label], 'xent')
# compute the expected loss
loss = model.AveragedLoss(xent, "loss")
# track the accuracy of the model
AddAccuracy(model, softmax, label)
# use the average loss we just computed to add gradient operators to the model
model.AddGradientOperators([loss])
# do a simple stochastic gradient descent
ITER = model.Iter("iter")
# set the learning rate schedule
LR = model.LearningRate(
    ITER, "LR", base_lr=-0.1, policy="step", stepsize=1, gamma=0.999 )
# ONE is a constant value that is used in the gradient update. We only need
# to create it once, so it is explicitly placed in param_init_net.
ONE = model.param_init_net.ConstantFill([], "ONE", shape=[1], value=1.0)
# Now, for each parameter, we do the gradient updates.
for param in model.params:
    # Note how we get the gradient of each parameter - ModelHelper keeps
    # track of that.
    param_grad = model.param_to_grad[param]
    # The update is a simple weighted sum: param = param + param_grad * LR
    model.WeightedSum([param, ONE, param_grad, LR], param)
# let's checkpoint every 20 iterations, which should probably be fine.
# you may need to delete tutorial_files/tutorial-mnist to re-run the tutorial
model.Checkpoint([ITER] + model.params, [],
               db="mnist_lenet_checkpoint_%05d.leveldb",
               db_type="leveldb", every=20)

接下來(lái),我們使用AddBookkeepingOperations來(lái)打印一些統(tǒng)計(jì)數(shù)據(jù)供我們之后觀察笼恰,這一部分不影響訓(xùn)練部分踊沸,只是統(tǒng)計(jì),打印日志社证。

def AddBookkeepingOperators(model):
    # Print basically prints out the content of the blob. to_file=1 routes the
    # printed output to a file. The file is going to be stored under
    #     root_folder/[blob name]
    model.Print('accuracy', [], to_file=1)
    model.Print('loss', [], to_file=1)
    # Summarizes the parameters. Different from Print, Summarize gives some
    # statistics of the parameter, such as mean, std, min and max.
    for param in model.params:
        model.Summarize(param, [], to_file=1)
        model.Summarize(model.param_to_grad[param], [], to_file=1)
    # Now, if we really want to be verbose, we can summarize EVERY blob
    # that the model produces; it is probably not a good idea, because that
    # is going to take time - summarization do not come for free. For this
    # demo, we will only show how to summarize the parameters and their
    # gradients.
print("Bookkeeping function created")

在這里逼龟,我們一共做了四件事:

(1)輸入部分(AddInput function)
(2)網(wǎng)絡(luò)計(jì)算部分(AddLeNetModel function)
(3)網(wǎng)絡(luò)訓(xùn)練部分,添加梯度運(yùn)算,更新等(AddTrainingOperators function)
(4)記錄統(tǒng)計(jì)部分追葡,打印一些統(tǒng)計(jì)數(shù)據(jù)來(lái)觀察(AddBookkeepingOperators function)

基本的操作我們都定義好了腺律,接下來(lái)調(diào)用定義模型,在這里宜肉,它定義了一個(gè)訓(xùn)練模型匀钧,用于訓(xùn)練,一個(gè)部署模型崖飘,用于部署:

arg_scope = {"order": "NCHW"}
train_model = model_helper.ModelHelper(name="mnist_train", arg_scope=arg_scope)
data, label = AddInput(
    train_model, batch_size=64,
    db=os.path.join(data_folder, 'mnist-train-nchw-leveldb'),
    db_type='leveldb')
softmax = AddLeNetModel(train_model, data)
AddTrainingOperators(train_model, softmax, label)
AddBookkeepingOperators(train_model)

# Testing model. We will set the batch size to 100, so that the testing
# pass is 100 iterations (10,000 images in total).
# For the testing model, we need the data input part, the main LeNetModel
# part, and an accuracy part. Note that init_params is set False because
# we will be using the parameters obtained from the train model.
test_model = model_helper.ModelHelper(
    name="mnist_test", arg_scope=arg_scope, init_params=False)
data, label = AddInput(
    test_model, batch_size=100,
    db=os.path.join(data_folder, 'mnist-test-nchw-leveldb'),
    db_type='leveldb')
softmax = AddLeNetModel(test_model, data)
AddAccuracy(test_model, softmax, label)

# Deployment model. We simply need the main LeNetModel part.
deploy_model = model_helper.ModelHelper(
    name="mnist_deploy", arg_scope=arg_scope, init_params=False)
AddLeNetModel(deploy_model, "data")
# You may wonder what happens with the param_init_net part of the deploy_model.
# No, we will not use them, since during deployment time we will not randomly
# initialize the parameters, but load the parameters from the db.

運(yùn)行網(wǎng)絡(luò)榴捡,打印loss曲線:

# The parameter initialization network only needs to be run once.
workspace.RunNetOnce(train_model.param_init_net)
# creating the network
workspace.CreateNet(train_model.net)
# set the number of iterations and track the accuracy & loss
total_iters = 200
accuracy = np.zeros(total_iters)
loss = np.zeros(total_iters)
# Now, we will manually run the network for 200 iterations.
for i in range(total_iters):
    workspace.RunNet(train_model.net.Proto().name)
    accuracy[i] = workspace.FetchBlob('accuracy')
    loss[i] = workspace.FetchBlob('loss')
# After the execution is done, let's plot the values.
pyplot.plot(loss, 'b')
pyplot.plot(accuracy, 'r')
pyplot.legend(('Loss', 'Accuracy'), loc='upper right')

[圖片上傳失敗...(image-d787eb-1529840965921)]

我們也可以輸出來(lái)預(yù)測(cè):

# Let's look at some of the data.
pyplot.figure()
data = workspace.FetchBlob('data')
_ = visualize.NCHW.ShowMultiple(data)
pyplot.figure()
softmax = workspace.FetchBlob('softmax')
_ = pyplot.plot(softmax[0], 'ro')
pyplot.title('Prediction for the first image')

[圖片上傳失敗...(image-7a001a-1529840965921)]

[圖片上傳失敗...(image-5e17ab-1529840965921)]

記得我們也定義了一個(gè)test_model杈女,我們可以運(yùn)行它得到測(cè)試集準(zhǔn)確率朱浴,雖然test_model的權(quán)值由train_model來(lái)加載,但是測(cè)試數(shù)據(jù)輸入還需要運(yùn)行param_init_net达椰。

# run a test pass on the test net
workspace.RunNetOnce(test_model.param_init_net)
workspace.CreateNet(test_model.net)
test_accuracy = np.zeros(100)
for i in range(100):
    workspace.RunNet(test_model.net.Proto().name)
    test_accuracy[i] = workspace.FetchBlob('accuracy')
# After the execution is done, let's plot the values.
pyplot.plot(test_accuracy, 'r')
pyplot.title('Acuracy over test batches.')
print('test_accuracy: %f' % test_accuracy.mean())

test_accuracy: 0.946700

[圖片上傳失敗...(image-13762-1529840965921)]

這樣翰蠢,我們就簡(jiǎn)單的完成了模型的搭建、訓(xùn)練啰劲、部署梁沧。

這個(gè)教程是caffe2的python接口教程。教程例子基本都是官方提供的蝇裤,只是加了些自己的理解思路廷支,也簡(jiǎn)單對(duì)比了caffe,可能有疏忽和理解錯(cuò)的地方栓辜,敬請(qǐng)指正恋拍。

作者:陸姚知馬力
鏈接:http://www.reibang.com/p/5c0fd1c9fef9
來(lái)源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處藕甩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末施敢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌僵娃,老刑警劉巖概作,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異默怨,居然都是意外死亡讯榕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門匙睹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瘩扼,“玉大人,你說(shuō)我怎么就攤上這事垃僚〖拢” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵谆棺,是天一觀的道長(zhǎng)栽燕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)改淑,這世上最難降的妖魔是什么碍岔? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮朵夏,結(jié)果婚禮上蔼啦,老公的妹妹穿的比我還像新娘。我一直安慰自己仰猖,他們只是感情好捏肢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饥侵,像睡著了一般鸵赫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躏升,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天辩棒,我揣著相機(jī)與錄音,去河邊找鬼膨疏。 笑死一睁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的佃却。 我是一名探鬼主播者吁,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼双霍!你這毒婦竟也來(lái)了砚偶?” 一聲冷哼從身側(cè)響起批销,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎染坯,沒(méi)想到半個(gè)月后均芽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡单鹿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年掀宋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仲锄。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劲妙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出儒喊,到底是詐尸還是另有隱情镣奋,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布怀愧,位于F島的核電站侨颈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏芯义。R本人自食惡果不足惜哈垢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扛拨。 院中可真熱鬧耘分,春花似錦、人聲如沸绑警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)待秃。三九已至拜秧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間章郁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工志衍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留暖庄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓楼肪,卻偏偏與公主長(zhǎng)得像培廓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子春叫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容