從零開始山寨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),但又有一些內容