1. 程式人生 > >從零開始山寨Caffe·伍:Protocol Buffer簡易指南

從零開始山寨Caffe·伍:Protocol Buffer簡易指南

你為Class外訪問private物件而苦惱嘛?你為設計序列化格式而頭疼嘛?

                            ——歡迎體驗Google Protocol Buffer

面向物件之封裝性

歷史遺留問題

面向物件中最矛盾的一個特性,就是“封裝性”。

在上古時期,大牛們無聊地設計了三種訪問域:

public、private、protected。

大多數C++初學者都是疑惑的,甚至是對於傳統C程式設計師而言。

在C規範中,沒有class(類)的概念,只有struct(結構體)的概念。

面向物件的C++中,儘管將C規範的struct移植過來了,但是這個struct是相當特殊的。

C++中的struct,和class沒有多大區別,可繼承/封裝/多型,也支援public/private/protected。

它只有一點不同,那就是預設訪問域是public,該設計僅僅是為了兼顧熟悉C規範的程式設計師。

C規範裡之所以沒有public/private/protected,因為它不是面嚮物件語言,沒有必要遵從OO的封裝性。

如果偏要讓C規範服從面向物件,那麼一切皆是public,這是C++中struct存在的意義。

程式設計規範

第壹章講到了Google程式設計師必須遵從的程式碼可讀標準,該標準主要體現在對變數的訪問上。

對於一次變數訪問行為,它是常(const)訪問,還是修改(mutable)訪問,這顯然是兩種行為。

由於變數只有一個,但訪問方式卻有兩種,於是軟體工程大師們認為,面向物件的訪問要以函式為載體。

這就產生了一種面向物件封裝性程式設計規範:

一切成員變數皆private,一切訪問方法皆public。

中間還有一個protected。protected的含義在不同語言裡是不同的(C++與Java就不同)。

在C++中,甚至在Caffe中,我們更鼓勵使用protected替代private。

具體來講,protected既包含private對外部訪問的遮蔽,又包含對繼承類的開放。

Caffe中廣泛使用繼承類設計,而private成員變數是不會被繼承的。

想象一下,Layer定義了引數W,但是繼承Layer的ConvLayer居然用不了引數W,這不是反人類麼?

讓我們來考慮一下程式碼量,設變數A在C規範中,宣告與定義佔用一行,

那麼在C++規範中,宣告與定義佔一行,const訪問至少佔一行(平均3行),mutable訪問至少佔一行(平均3行)。

這樣,為了這個裝逼的封裝性,我們的程式碼量平均要上去5倍左右。尤其是在機器學習系統中,大量資料結構的情況下,

原始碼中將會充斥著大量這類無聊的get(const訪問)函式,set(mutable訪問)函式,不得不說,是挺無奈的事。

序列化

文字資料與序列化

喜歡玩遊戲的,應該都改過類似於config.ini的檔案。

比如我手裡的《輻射4》根目錄下的Ultra.ini,就提供了編輯顯示配置的高階方式。

大部分Application Framework都提供了對INI檔案的解析(Parse)。

其實這並不是難事,學過《編譯原理》的人,應該都做過詞法分析器的實驗。

編譯器的詞法分析,論本質,它其實也是人工智慧(AI),只不過它的智慧必須基於特定規則。

歸根結底,還是沒有超出馮諾依曼的儲存程式智慧範疇,離圖靈的無敵圖靈機還遠得很。

解析平面結構的文字是簡單的,如圖,INI檔案只由域[XXX],和域下配置項組成。

如果是層次結構呢,比如XML?當然XML有其專門的語法樹。

XML語法相當冗繁,看起來就像是機器寫的(實際上大部分XML真是機器寫的)。

在一個機器學習系統中,顯然我們需要層次資料結構的配置。

比如Caffe中經典的層次結構:

solver{

  net{

    layer{

      blob{

考慮一個更特殊的情況,solver配置和net配置顯然需要寫在不同檔案裡,增強遷移性。

XML解析器顯然沒有這麼高階的功能,能夠整合多個XML檔案。

這樣,XML解析器之上,起碼還需要二次程式設計,相當坑爹。

格式化資料與序列化

何為格式化資料?簡而言之,就是:

C++寫的東西,Python能用,MATLAB也能用。

目前廣泛使用的格式化資料主要有兩種,Binary(C++、Python)、HDF5(MATLAB)。

你肯定會問,ACM比賽不都是用文字格式存資料,為什麼不用文字格式做格式化資料?

答案其實很無語:文字格式的體積要比二進位制格式體積大5倍左右,讀取速度也要相應慢上幾倍。

所以,一個機器學習系統,可以從文字IN資料,但是千萬不要嘗試將資料OUT成文字格式。

文字格式除了體積問題,還存在安全性問題。文字型資料很容易被逆向破解掉。

相反,二進位制等格式易於做位運算的特點,非常適合,且基本支援二進位制序列化的API,

都對二進位制資料進行了加密(比如Qt的QDataStream),當然安全性不是我們考慮的重點。

二進位制雖然體積小,但是需要人工設計封裝格式。這給序列化(編碼),反序列(解碼),帶來麻煩。

在傳統C++大型程式中,我們都能看到序列化和反序列化程式碼相當冗長。

程式設計師寫到最後,都不知道自己到底IN進了什麼資料,OUT出了什麼資料,程式碼顯得十分笨拙。

尤其是在機器學習系統中,考慮到我們需要將引數W儲存到硬碟。

首先,引數W有多少個?是什麼格式?順序是什麼?這些都要先記錄。

記錄完了之後,才能將最寶貴的引數W寫到檔案,是不是很蠢,很蠢,很蠢?

Google Protocol Buffer

不錯的工具

Protocol Buffer是由Jeff Dean領銜開發的神奇工具。

它不僅有著非常不錯的格式化資料的序列化/反序列速度,同時也支援文字格式。

更重要的是,它在自動生成序列化格式的同時,也封裝了部分變數的訪問介面。

使得Caffe的整體原始碼中,不必充斥著大量的get/set。

最後,Jeff Dean出品,速度必然是有保障的。

這位Google首席技術員,PHD專攻編譯器優化,被譽為是地球上讓程式碼跑的最快的男人。

使用方法

這玩意在牆外,在第零章提供的包裡,3rdparty\bin下protoc.exe就是在Windows下本體。

確保3rdparty\bin在環境變數中,編輯proto-make.cmd指令碼:

@echo off
set SRC_DIR=C:\PROTO
set DST_DIR=C:\PROTO
set PROTO_NAME=dragon
echo Check Source Proto Path:  %SRC_DIR%
echo Check Destination Proto Path:  %DST_DIR%
echo Check Proto Files Name :  %PROTO_NAME%.proto
echo ——————————————————————————————————
echo Protocol Buffer:Compliing for dragon.proto.....
start protoc -I=%SRC_DIR% --cpp_out=%DST_DIR% %SRC_DIR%\%PROTO_NAME%.proto
echo Protocol Buffer:Compliing complete!
pause

SRC_DIR為proto指令碼的源路徑,DST_DIR為生成路徑。

proto指令碼是操縱protoc.exe的唯一方式,Google為proto指令碼設計了一種新的語言,非常類似於C/C++。

protoc版本會根據proto指令碼生成h和cc檔案,分別是資料結構的宣告和定義,隨時可以嵌入到你的程式碼中。

protoc的命令引數摘自牆外的官網,我們通常只需要設定源目錄、目標目錄、以及proto指令碼路徑:

protoc -I=%SRC_DIR% --cpp_out=%DST_DIR% %SRC_DIR%\%PROTO_NAME%.proto

第一步

在你喜歡的源目錄下,新建dragon.proto,用文字編輯器開啟它,

定義第一個資料結構Datum:

message Datum{
    optional int32 channels=1;
    optional int32 height=2;
    optional int32 width=3;
    optional int32 label=4;
    optional bytes data=5;
    repeated float float_data=6;
    optional bool encoded=7 [default=false];
}

Datum算是最基本的儲存單元了,它其實表示的就是一張影象。

proto語言與C語言差別不是很大,結構體struct欄位換成message,

變數之前需要追加optional和repeated標記欄位。分別表示的是單變數,還是容器陣列變數。

值得一提的是,proto提供requireed欄位,但是Google程式設計師都懶得用,經常會出現奇怪bug,

所以一律用optional替代requireed。

repeated標記之後,本質是陣列,但實際實現可能是類似於STL容器,它提供了不少類似容器的操作。

[default]可以提供預設值,對於基本資料型別,不設預設值將會同C語言一樣產生類似預設值。

但我們不推薦使用proto自身提供的預設值,通常會之前接一個has_xxx(),來檢測該變數是否被設定。

人工指定的預設值,has_xxx()會返回true,而proto提供的自動預設值,則是false。

另外,對於repeated int32 or int64,使用[packed=true]似乎可以優化速度,對於float其實是無效的。

Caffe裡有些repeat float也打上了[packed=true],其實沒什麼意義。

最後,所有資料結構變數,都需要一個唯一的id,id從1開始。

這與proto內部編碼系統有關,1~20編碼長度小,訪問速度快。隨著id值增加,後續變數訪問速度會遞減。

再看Datum本身,channels、height、width都是我們熟悉的。

data和float_data的區別在於,前者用於uint8資料,比如MNIST和cifar10/100,

它們的畫素值可以被壓縮為一個字串,而bytes型別在C++裡,恰好就是string型別。

float_data則用於儲存散裝的float值了。

最後的encoded可以被忽略,我還沒見過什麼影象需要編碼的。

Caffe需要OpenCV,主要是由於考慮到影象需要解碼,省略這一步,OpenCV可以無視掉。

第二步

我們還需要為Blob提供一個序列化容器,用於儲存訓練引數。

message BlobShape{
    repeated int64 dim=1 [packed=true];
}

message BlobProto{
    optional BlobShape shape=1;
    repeated float data=2;
    repeated float diff=3;
    repeated double double_data=4;
    repeated double double_diff=5;
}

BlobShape用於儲存Blob Shape資訊。

BlobProto才是我們需要關注的,除了shape,它由四個容器陣列組成。

大部分情況下,我們只會使用其中兩個。

因為只有Tesla系列顯示卡,才支援double運算,而GTX玩家顯示卡,只能使用float運算。

data用於儲存引數資料,diff用於儲存殘差,實際上diff基本是不會用的,記錄引數的殘差沒有多少意義。

完整程式碼

相關推薦

開始山寨Caffe·Protocol Buffer簡易指南

你為Class外訪問private物件而苦惱嘛?你為設計序列化格式而頭疼嘛?                             ——歡迎體驗Google Protocol Buffer 面向物件之封裝性 歷史遺留問題 面向物件中最矛盾的一個特性,就是“封裝性”。 在上古時期,大牛們無聊地設計了

開始山寨Caffe·貳主存模型

本文轉自:https://www.cnblogs.com/neopenx/p/5190282.html 從硬體說起 物理之觴 大部分Caffe原始碼解讀都喜歡跳過這部分,我不知道他們是什麼心態,因為這恰恰是最重要的一部分。 記憶體的管理不擅,不僅會導致程式的立即崩潰,還會導致記憶體的

開始山寨Caffe·柒KV資料庫

你說你會關係資料庫?你說你會Hadoop? 忘掉它們吧,我們既不需要網路支援,也不需要複雜關係模式,只要讀寫夠快就行。                                         ——論資料儲存的本質 淺析資料庫技術 記憶體資料庫——STL的map容器 關係資料庫橫行已久,似乎大

開始山寨Caffe·玖BlobFlow

聽說Google出了TensorFlow,那麼Caffe應該叫什麼?                           ——BlobFlow 神經網路時代的傳播資料結構 我的程式碼 我最早手寫神經網路的時候,Flow結構是這樣的: struct Data { vector<d

開始山寨Caffe·陸IO系統(一)

你說你學過作業系統這門課?寫個無Bug的生產者和消費者模型試試!                               ——你真的學好了作業系統這門課嘛? 在第壹章,展示過這樣圖: 其中,左半部分構成了新版Caffe最惱人、最龐大的IO系統。 也是歷來最不重視的一部分。 第伍章又對左半

開始山寨Caffe·捌IO系統(二)

生產者 雙緩衝組與訊號量機制 在第陸章中提到了,如何模擬,以及取代根本不存的Q.full()函式。 其本質是:除了為生產者提供一個成品緩衝佇列,還提供一個零件緩衝佇列。 當我們從外部給定了固定容量的零件之後,生產者的產能就受到了限制。 由兩個阻塞佇列組成的QueuePair,並不是Caffe的獨創,

開始山寨Caffe·拾IO系統(三)

資料變形 IO(二)中,我們已經將原始資料緩衝至Datum,Datum又存入了生產者緩衝區,不過,這離消費,還早得很呢。 在消費(使用)之前,最重要的一步,就是資料變形。 ImageNet ImageNet提供的資料相當Raw,不僅影象尺寸不一,ROI焦點內容比例也不一,如圖: [Krizhev

開始山寨Caffe·拾貳IO系統(四)

消費者 回憶:生產者提供產品的介面 在第捌章,IO系統(二)中,生產者DataReader提供了外部消費介面: class DataReader { public: ......... BlockingQueue<Datum*>& free() const

開始caffe(七)利用GoogleNet實現影象識別

一、準備模型 在這裡,我們利用已經訓練好的Googlenet進行物體影象的識別,進入Googlenet的GitHub地址,進入models資料夾,選擇Googlenet 點選Googlenet的模型下載地址下載該模型到電腦中。 模型結構 在這裡,我們利用之前講

開始caffe(十)caffe中snashop的使用

在caffe的訓練期間,我們有時候會遇到一些不可控的以外導致訓練停止(如停電、裝置故障燈),我們就不得不重新開始訓練,這對於一些大型專案而言是非常致命的。在這裡,我們介紹一些caffe中的snashop。利用snashop我們就可以實現訓練的繼續進行。 在之前我們訓練得到的檔案中,我們發現

開始caffe(九)在Windows下實現影象識別

本系列文章主要介紹了在win10系統下caffe的安裝編譯,運用CPU和GPU完成簡單的小專案,文章之間具有一定延續性。 step1:準備資料集 資料集是進行深度學習的第一步,在這裡我們從以下五個連結中下載所需要的資料集: animal flower plane hou

開始caffe(八)Caffe在Windows環境下GPU版本的安裝

之前我們已經安裝過caffe的CPU版本,但是在MNIST手寫數字識別中,我們發現caffe的CPU版本執行速度較慢,訓練效率不高。因此,在這裡我們安裝了caffe的GPU版本,並使用GPU版本的caffe同樣對手寫MNIST數字集進行訓練。 step1: 安裝CUDA

開始caffe(四)mnist手寫數字識別網路結構模型和超引數檔案的原始碼閱讀

下面為網路結構模型 %網路結構模型 name: "LeNet" #網路的名字"LeNet" layer { #定義一個層 name: "mnist" #層的名字"mnist" type:

開始caffe(二)caffe在win10下的安裝編譯

環境要求 作業系統:64位windows10 編譯環境:Visual Studio 2013 Ultimate版本 安裝流程 step1:檔案的下載 從GitHub新增連結描述中下載Windows版本的caffe,並進行解壓到電腦中。 step2:檔案修改 將壓縮包

開始系列-Caffe入門到精通之一 環境搭建

python 資源暫時不可用 強制 rec htm color 查看 cpu blog 先介紹下電腦軟硬件情況吧: 處理器:Intel? Core? i5-2450M CPU @ 2.50GHz × 4 內存:4G 操作系統:Ubuntu Kylin(優麒麟) 16.04

Redis開始學習教程三key值的有效期

圖片 com edi 數據 key值 一次 時間 inf 系統 Redis 是一種存儲系統,類似數據庫,和緩存的差別是,緩存有有效期,而Redis默認無有效期,或者說,默認有效期為永久 但是Redis可以當做緩存使用。這時候需要針對各個key設置有效期。 有效期單位默認為S

【視訊】Kubernetes1.12開始(六)程式碼編譯到自動部署

作者: 李佶澳   轉載請保留:原文地址   釋出時間:2018/11/10 16:14:00 說明 kubefromscratch-ansible和kubefromscratch介紹 使用前準備

開始理解caffe網路的引數

LeNet網路介紹 LeNet網路詳解 網路名稱 name: "LeNet" # 網路(NET)名稱為LeNet mnist層-train layer {

開始學習Servlet(1) 作用和生命週期

Servlet 作用 Servlet 是實現了 javax.servlet.Servlet 介面的 Java 類, 負責處理客戶端的 HTTP 請求。是客戶端 與 資料庫或後臺應用程式之間互動的媒介 。功能: 1. 讀取客戶端傳送的資料 2. 處理

ubuntu 14.04 開始安裝caffe

一、前言 很多人不太喜歡看官方教程,但其實 caffe 的官方安裝指導做的非常好。我在看到 2) 之前,曾根據官方指導在 OSX 10.9, 10.10, Ubuntu 12.04, 14.04 下安裝過 10 多次不同版本的 caffe,都成功了。 本文有不少內容參考了 1)和 2),但又有一些內容