Caffe2 手寫字符識別(MNIST - Create a CNN from Scratch)[8]


本教程創建一個小的神經網絡用於手寫字符的識別。我們使用MNIST數據集進行訓練和測試。這個數據集的訓練集包含60000張來自500個人的手寫字符的圖像,測試集包含10000張獨立於訓練集的測試圖像。你可以參看本教程的Ipython notebook

本節中,我們使用CNN的模型助手來創建網絡並初始化參數。首先import所需要的依賴庫。

%matplotlib inline
from matplotlib import pyplot
import numpy as np
import os
import shutil
from caffe2.python import core, cnn, net_drawer, workspace, visualize
# 如果你想更加詳細的了解初始化的過程,那么你可以把caffe2_log_level=0 改為-1
core.GlobalInit(['caffe2', '--caffe2_log_level=0'])
caffe2_root = "~/caffe2"
print("Necessities imported!")
數據准備

我們會跟蹤訓練過程的數據,並保存到一個本地的文件夾。我們需要先設置一個數據文件和根文件夾。在數據文件夾里,放置用於訓練和測試的MNIST數據集。如果沒有數據集,那么你可以到這里下載MNIST Dataset,然后解壓數據集和標簽。

./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/train-images-idx3-ubyte --label_file ~/Downloads/train-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/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/caffe2/python/tutorials/tutorial_data/mnist/mnist-test-nchw-leveldb

這段代碼實現和上面的一樣的功能

# 這部分將你的圖像轉換成leveldb
current_folder = os.getcwd()
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")

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)
if not os.path.exists(data_folder):
    os.makedirs(data_folder)
if not os.path.exists(label_file_train):
    DownloadDataset("https://s3.amazonaws.com/caffe2/datasets/mnist/mnist.zip", data_folder)

def GenerateDB(image, label, name):
    name = os.path.join(data_folder, name)
    print 'DB name: ', name
    syscall = "/usr/local/binaries/make_mnist_db --channel_first --db leveldb --image_file " + image + " --label_file " + label + " --output_file " + name
    print "Creating database with: ", syscall
    os.system(syscall)

# 生成leveldb
GenerateDB(image_file_train, label_file_train, "mnist-train-nchw-leveldb")
GenerateDB(image_file_test, label_file_test, "mnist-test-nchw-leveldb")

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

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

print("training data folder:"+data_folder)
print("workspace root folder:"+root_folder)
模型創建

CNNModelHelper封裝了很多函數,它能將參數初始化和真實的計算分成兩個網絡中實現。底層實現是,CNNModelHelper有兩個網絡param_init_netnet,這兩個網絡分別記錄着初始化網絡和主網絡。為了模塊化,我們將模型分割成多個不同的部分。
- 數據輸入(AddInput 函數)
- 主要的計算部分(AddLeNetModel 函數)
- 訓練部分-梯度操作,參數更新等等 (AddTrainingOperators函數)
- 記錄數據部分,比如需要展示訓練過程的相關數據(AddBookkeepingOperators 函數)

  1. AddInput會從一個DB中載入數據。我們將MNIST保存為像素值,並且我們用浮點數進行計算,所以我們的數據也必須是Float類型。為了數值穩定性,我們將圖像數據歸一化到[0,1]而不是[0,255]。注意,我們做的事in-place操作,會覆蓋原來的數據,因為我們不需要歸一化前的數據。准備數據這個操作,在后向傳播時,不需要進行梯度計算。所以我們使用StopGradient來告訴梯度生成器:“不用將梯度傳遞給我。”
def AddInput(model, batch_size, db, db_type):
    # 載入數據和標簽
    data_uint8, label = model.TensorProtosDBInput(
        [], ["data_uint8", "label"], batch_size=batch_size,
        db=db, db_type=db_type)
    # 轉化為 float
    data = model.Cast(data_uint8, "data", to=core.DataType.FLOAT)
    #歸一化到 [0,1]
    data = model.Scale(data, data, scale=float(1./256))
    # 后向傳播不需要梯度
    data = model.StopGradient(data, data)
    return data, label
print("Input function created.")

輸出

Input function created.
  1. AddLeNetModel輸出softmax.
def AddLeNetModel(model, data):
    conv1 = model.Conv(data, 'conv1', 1, 20, 5)
    pool1 = model.MaxPool(conv1, 'pool1', kernel=2, stride=2)
    conv2 = model.Conv(pool1, 'conv2', 20, 50, 5)
    pool2 = model.MaxPool(conv2, 'pool2', kernel=2, stride=2)
    fc3 = model.FC(pool2, 'fc3', 50 * 4 * 4, 500)
    fc3 = model.Relu(fc3, fc3)
    pred = model.FC(fc3, 'pred', 500, 10)
    softmax = model.Softmax(pred, 'softmax')
    return softmax
print("Model function created.")
Model function created.
  1. AddTrainingOperators函數函數用於添加訓練操作。
    AddAccuracy函數輸出模型的准確率,我們會在下一個函數使用它來跟蹤准確率。
def AddAccuracy(model, softmax, label):
    accuracy = model.Accuracy([softmax, label], "accuracy")
    return accuracy
print("Accuracy function created.")
Accuracy function created.

首先添加一個op:LabelCrossEntropy,用於計算輸入和lebel的交叉熵。這個操作在得到softmax后和計算loss前。輸入是[softmax, label],輸出交叉熵用xent表示。

xent = model.LabelCrossEntropy([softmax, label], 'xent')

AveragedLoss將交叉熵作為輸入,並計算出平均損失loss

loss = model.AveragedLoss(xent, "loss")

AddAccuracy為了記錄訓練過程,我們使用AddAccuracy 函數來計算。

AddAccuracy(model, softmax, label)

接下來這步至關重要:我們把所有梯度計算添加到模型上。梯度是根據我們前面的loss計算得到的。

model.AddGradientOperators([loss])

然后進入迭代

ITER = model.Iter("iter")

更新學習率使用策略是lr = base_lr * (t ^ gamma) ,注意我們是在最小化,所以基礎學率是負數,這樣我們才能向山下走。

LR = model.LearningRate(ITER, "LR", base_lr=-0.1, policy="step", stepsize=1, gamma=0.999 ) 
#ONE是一個在梯度更新階段用的常量。只需要創建一次,並放在param_init_net中。
ONE = model.param_init_net.ConstantFill([], "ONE", shape=[1], value=1.0)

現在對於每一和參數,我們做梯度更新。注意我們如何獲取每個參數的梯度——CNNModelHelper保持跟蹤這些信息。更新的方式很簡單,是簡單的相加: param = param + param_grad * LR

for param in model.params:
    param_grad = model.param_to_grad[param]
    model.WeightedSum([param, ONE, param_grad, LR], param)   

我們需要每隔一段時間檢查參數。這可以通過Checkpoint 操作。這個操作有一個參數every表示每多少次迭代進行一次這個操作,防止太頻繁去檢查。這里,我們每20次迭代進行一次檢查。

model.Checkpoint([ITER] + model.params, [],
               db="mnist_lenet_checkpoint_%05d.leveldb",
               db_type="leveldb", every=20)

然后我們得到整個AddTrainingOperators函數如下:

def AddTrainingOperators(model, softmax, label):
    # 計算交叉熵
    xent = model.LabelCrossEntropy([softmax, label], 'xent')
    # 計算loss
    loss = model.AveragedLoss(xent, "loss")
    #跟蹤模型的准確率
    AddAccuracy(model, softmax, label)
    #添加梯度操作
    model.AddGradientOperators([loss])
    # 梯度下降
    ITER = model.Iter("iter")
    # 學習率
    LR = model.LearningRate(
        ITER, "LR", base_lr=-0.1, policy="step", stepsize=1, gamma=0.999 )
    ONE = model.param_init_net.ConstantFill([], "ONE", shape=[1], value=1.0)
    # 梯度更新
    for param in model.params:
        param_grad = model.param_to_grad[param]
        model.WeightedSum([param, ONE, param_grad, LR], param)
    # 每迭代20次檢查一次
    # 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)
print("Training function created.")
Training function created.
  1. **AddBookkeepingOperators **添加一些記錄操作,這些操作不會影響訓練過程。他們只是收集數據和打印出來或者寫到log里面去。
def AddBookkeepingOperators(model):
    # 輸出 blob的內容. to_file=1 表示輸出到文件,文件保存的路徑是 root_folder/[blob name]
    model.Print('accuracy', [], to_file=1)
    model.Print('loss', [], to_file=1)
    # Summarizes 給出一些參數比如均值,方差,最大值,最小值
    for param in model.params:
        model.Summarize(param, [], to_file=1)
        model.Summarize(model.param_to_grad[param], [], to_file=1)
print("Bookkeeping function created")
  1. 定義網絡
    現在讓我們將真正創建模型。前面寫的函數將真正被執行。回憶我們四步。
-data input  
-main computation
-training
-bookkeeping

在我們讀進數據前,我們需要定義我們訓練模型。我們將使用到前面定義的所有東西。我們將在MNIST數據集上使用NCHW的儲存順序。

train_model = cnn.CNNModelHelper(order="NCHW", name="mnist_train")
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. 我們設置batch=100,這樣迭代100次就能覆蓋10000張測試圖像
# 對於測試模型,我們需要數據輸入 ,LeNetModel,和准確率三部分
#注意到init_params 設置為False,是因為我們從訓練網絡獲取參數。
test_model = cnn.CNNModelHelper(order="NCHW", name="mnist_test", 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. 我們僅需要LeNetModel 部分
deploy_model = cnn.CNNModelHelper(order="NCHW", name="mnist_deploy", init_params=False)
AddLeNetModel(deploy_model, "data")
#你可能好奇deploy_model的param_init_net 發生了什么,在這節中,我們沒有使用它,
#因為在deployment 階段,我們不會隨機初始化參數,而是從本地載入。
print('Created training and deploy models.')

現在讓我們用caffe2的可視化工具看看Training和Deploy模型是什么樣子的。如果下面的命令運行失敗,那可能是因為你的機器沒有安裝graphviz。可以用如下命令安裝:

sudo yum install graphviz #ubuntu 用戶sudo apt-get install graphviz 

圖看起來可能很小,右鍵點擊在新的窗口打開就能看清。

from IPython import display
graph = net_drawer.GetPydotGraph(train_model.net.Proto().op, "mnist", rankdir="LR")
display.Image(graph.create_png(), width=800)


現在上圖展示了訓練階段的一切東西。白色的節點是blobs,綠色的矩形節點是operators.你可能留意到大規模的像火車軌道一樣的平行線。這些依賴關系從前前向傳播的blobs指向到后向傳播的操作。
讓我們僅僅展示必要的依賴關系和操作。如果你細心看,你會發現,左半圖式前向傳播,右半圖式后向傳播,在最右邊是一系列參數更新操作和summarization .

graph = net_drawer.GetPydotGraphMinimal(
    train_model.net.Proto().op, "mnist", rankdir="LR", minimal_dependency=True)
display.Image(graph.create_png(), width=800)


現在我們可以通過Python來跑起網絡,記住,當我們跑起網絡時,我們隨時可以從網絡中拿出blob數據,下面先來展示下如何進行這個操作。

我們重申一下,CNNModelHelper 類目前沒有執行任何東西。他目前做的僅僅是聲明網絡,只是簡單的創建了protocol buffers.例如我們可以展示網絡一部分序列化的protobuf。

print(str(train_model.param_init_net.Proto())[:400] + '\n...')

當然,我們也可以把protobuf寫到本地磁盤中去,這樣可以方便的查看。你會發現這些protobuf和以前的Caffe網絡定義很相似。

with open(os.path.join(root_folder, "train_net.pbtxt"), 'w') as fid:
    fid.write(str(train_model.net.Proto()))
with open(os.path.join(root_folder, "train_init_net.pbtxt"), 'w') as fid:
    fid.write(str(train_model.param_init_net.Proto()))
with open(os.path.join(root_folder, "test_net.pbtxt"), 'w') as fid:
    fid.write(str(test_model.net.Proto()))
with open(os.path.join(root_folder, "test_init_net.pbtxt"), 'w') as fid:
    fid.write(str(test_model.param_init_net.Proto()))
with open(os.path.join(root_folder, "deploy_net.pbtxt"), 'w') as fid:
    fid.write(str(deploy_model.net.Proto()))
print("Protocol buffers files have been created in your root folder: "+root_folder)

現在,讓我們進入訓練過程。我們使用Python來訓練。當然也可以使用C++接口來訓練。這留在另一個教程討論。

訓練網絡

首先,初始化網絡是必須的

workspace.RunNetOnce(train_model.param_init_net)

接着我們創建訓練網絡並,加載到workspace中去。

workspace.CreateNet(train_model.net)

然后設置迭代200次,並把准確率和loss保存到兩個np矩陣中去

total_iters = 200
accuracy = np.zeros(total_iters)
loss = np.zeros(total_iters)

網絡和跟蹤准確loss都配置好后,我們循環調用workspace.RunNet200次,需要傳入的參數是train_model.net.Proto().name.每一次迭代,我們計算准確率和loss。

for i in range(total_iters):
    workspace.RunNet(train_model.net.Proto().name)
    accuracy[i] = workspace.FetchBlob('accuracy')
    loss[i] = workspace.FetchBlob('loss')

最后我們可以用pyplot畫出結果。

# 參數初始化只需跑一次
workspace.RunNetOnce(train_model.param_init_net)
# 創建網絡
workspace.CreateNet(train_model.net)
#設置迭代數和跟蹤accuracy & loss
total_iters = 200
accuracy = np.zeros(total_iters)
loss = np.zeros(total_iters)
# 我們迭代200次
for i in range(total_iters):
    workspace.RunNet(train_model.net.Proto().name)
    accuracy[i] = workspace.FetchBlob('accuracy')
    loss[i] = workspace.FetchBlob('loss')
# 迭代完畫出結果
pyplot.plot(loss, 'b')
pyplot.plot(accuracy, 'r')
pyplot.legend(('Loss', 'Accuracy'), loc='upper right')


現我們可以進行抽取數據和預測了

#數據可視化
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')



還記得我們創建的test net嗎?我們將跑一遍test net測試准確率。**注意,雖然test_model的參數來自train_model,但是仍然需要初始化test_model.param_init_net **。這次,我們只需要追蹤准確率,並且只迭代100次。

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')
pyplot.plot(test_accuracy, 'r')
pyplot.title('Acuracy over test batches.')
print('test_accuracy: %f' % test_accuracy.mean())


譯者注:這里譯者不是很明白,test_model是如何從train_model獲取參數的?有明白的小伙伴希望能在評論區分享一下。

MNIST教程就此結束。希望本教程能向你展示一些Caffe2的特征。

轉載請注明出處:http://www.jianshu.com/c/cf07b31bb5f2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM