1. 程式人生 > >Caffe的深度學習訓練全過程

Caffe的深度學習訓練全過程

本文為大資料雜談4月20日微信社群分享內容整理。

今天的目標是使用Caffe完成深度學習訓練的全過程。Caffe是一款十分知名的深度學習框架,由加州大學伯克利分校的賈揚清博士於2013年在Github上釋出。自那時起,Caffe在研究界和工業界都受到了極大的關注。Caffe的使用比較簡單,程式碼易於擴充套件,執行速度得到了工業界的認可,同時還有十分成熟的社群。

對於剛開始學習深度學習的同學來說,Caffe是一款十分十分適合的開源框架。與其他同類型的框架相比,它的一個最大的特點,就是程式碼和框架比較簡單,適合深入瞭解分析。今天將要介紹的內容都是Caffe中成型很久的內容,如今絕大多數版本的Caffe都包含這些功能。關於Caffe下載和安裝的內容請各位根據官方網站指導進行下載和安裝,這裡就不再贅述了。

一個常規的監督學習任務主要包含訓練與預測兩個大的步驟,這裡還是以Caffe中自帶的例子——MNIST資料集手寫數字識別為例,來介紹一下它具體的使用方法。

如果把上面提到的深度學習訓練步驟分解得更細緻一些,那麼這個常規流程將分成這幾個子步驟:

  1. 資料預處理(建立資料庫)
  2. 網路結構與模型訓練的配置
  3. 訓練與在訓練
  4. 訓練日誌分析
  5. 預測檢驗與分析
  6. 效能測試

下面就來一一介紹。

1 資料預處理

首先是訓練資料和預測資料的預處理。這裡的工作一般是把待分析識別的影象進行簡單的預處理,然後儲存到資料庫中。為什麼要完成這一步而不是直接從影象檔案中讀取資料呢?因為實際任務中訓練資料的數量可能非常大,從影象檔案中讀取資料並進行初始化的效率是非常低的,所以很有必要把資料預先儲存在資料庫中,來加快訓練的節奏。

以下的操作將全部在終端完成。第一步是將資料下載到本地,好在MNIST的資料量不算大,如果大家的網路環境好,這一步的速度會非常快。首先來到caffe的安裝根目錄——CAFFE_HOME,然後執行下面的命令:

cd data/mnist
./get_mnist.sh

程式執行完成後,資料夾下應該會多出來四個檔案,這四個檔案就是我們下載的資料檔案。第二步我們需要呼叫example中的資料庫建立程式:

cd $CAFFE_HOME
./examples/mnist/create_mnist.sh

程式執行完成後,examples/mnist資料夾下面就會多出兩個資料夾,分別儲存了MNIST的訓練和測試資料。值得一提的是,資料庫的格式可以通過修改指令碼的BACKEND變數來更換。目前資料庫有兩種主流選擇:

  • LevelDB
  • LmDB

這兩種資料庫在儲存資料和操縱上有一些不同,首先是它們的資料組織方式不同,這是LevelDB的內容:

這是LMDB的內容:

從結構可以看出LevelDB的檔案比較多,LMDB的檔案更為緊湊。

其次是它們的讀取資料的介面,某些場景需要遍歷資料庫完成一些原始影象的分析處理,因此瞭解它們的資料讀取方法也十分有必要。首先是LMDB讀取資料的程式碼:

其次是LevelDB讀取的程式碼:

最後回到本小節的問題:為什麼要採用資料庫的方式儲存資料而不是直接讀取影象?這裡可以簡單測試一下用MNIST資料構建的這兩個資料庫按序讀取的速度,這裡用系統函式time進行計時,結果如下:

為了比較原始影象讀入的速度,這裡將MNIST的資料以jpeg的格式儲存成影象,並測試它的讀取效率(以Caffe python使用的scikit image為例),程式碼如下所示:

最終的時間如下所示:

由此可以看出,原始影象和資料庫相比,讀取資料的效率差距還是蠻大的。雖然在Caffe訓練中資料讀入是非同步完成的,但是它還是不能夠太慢,所以這也是在訓練時選擇資料庫的原因。

至於這兩個資料庫之間的比較,這裡就不再多做了。感興趣的各位可以在一些大型的資料集上做一些實驗,那樣更容易看出兩個資料集之間的區別。

2 網路結構與模型訓練的配置

上一節完成了資料庫的建立,下面就要為訓練模型做準備了。一般來說Caffe採用讀入配置檔案的方式進行訓練。Caffe的配置檔案一般由兩部分組成:solver.prototxt和net.prototxt(有時會有多個net.prototxt)。它們實際上對應了Caffe系統架構中兩個十分關鍵的實體——網路結構Net和求解器Solver。先來看看一般來說相對簡短的solver.prototxt的內容,為了方便大家理解,所有配置資訊都已經加入了註釋:

為了方便大家理解,這裡將examples/mnist/lenet_solver.prototxt中的內容進行重新排序,整個配置檔案相當於回答了下面幾個問題:

  • 網路結構的檔案在哪?
  • 用什麼計算資源訓練?CPU還是GPU?
  • 訓練多久?訓練和測試的比例是如何安排的,什麼時候輸出些給我們瞧瞧?
  • 優化的學習率怎麼設定?還有其他的優化引數——如動量和正則呢?
  • 要時刻記得存檔啊,不然大俠得從頭來過了……

接下來就是net.prototxt了,這裡忽略了每個網路層的引數配置,只把表示網路的基本結構和型別配置展示出來:

name: "LeNet"
layer {
  name: "mnist"        
  type: "Data"    
  top: "data"        
  top: "label"
}
layer {
  name: "conv1"        
  type: "Convolution"        
  bottom: "data"        
  top: "conv1"
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
}
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
}
layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "pool2"
  top: "ip1"
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

這裡只展示了網路結構的基礎配置,也佔用了大量的篇幅。一般來說,這個檔案中的內容超過100行都是再常見不過的事。而像大名鼎鼎的ResNet網路,它的檔案長度通常在千行以上,更是讓人難以閱讀。那麼問題來了,那麼大的網路檔案都是靠人直接編輯出來的麼?不一定。有的人會比較有耐心地一點點寫完,而有的人則不會願意做這樣的苦力活。實際上Caffe提供了一套介面,大家可以通過寫程式碼的形式生成這個檔案。這樣一來,編寫模型配置的工作也變得簡單不少。下面展示了一段生成LeNet網路結構的程式碼:

最終生成的結果大家都熟知,這裡就不給出了。

layer {
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  transform_param {
    scale: 0.00390625
    mirror: false
  }
  data_param {
    source: "123"
    batch_size: 128
    backend: LMDB
  }
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "pool2"
  top: "ip1"
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

大家可能覺得上面的程式碼並沒有節省太多篇幅,實際上如果將上面的程式碼模組化做得更好些,它就會變得非常簡潔。這裡就不做演示了,歡迎大家自行嘗試。

3 訓練與再訓練

準備好了資料,也確定了訓練相關的配置,下面正式開始訓練。訓練需要啟動這個指令碼:

然後經過一段時間的訓練,命令列產生了大量日誌,訓練過程也宣告完成。這時訓練好的模型目錄多出了這幾個檔案:

很顯然,這幾個檔案儲存了訓練過程中的一些內容,那麼它們都是做什麼的呢?*caffemodel*檔案儲存了caffe模型中的引數,*solverstate*檔案儲存了訓練過程中的一些中間結果。儲存引數這件事情很容易想象,但是儲存訓練中的中間結果就有些抽象了。solverstate裡面究竟儲存了什麼?回答這個問題就需要找到solverstate的內容定義,這個定義來自src/caffe/proto/caffe.proto檔案:

從定義中可以很清楚的看出其內容的含義。其中history是一個比較有意思的資訊,他儲存了歷史的引數優化資訊。這個資訊有什麼作用呢?由於很多演算法都依賴歷史更新資訊,如果有一個模型訓練了一半停止了下來,現在想基於之前訓練的成果繼續訓練,那麼需要歷史的優化資訊幫助繼續訓練。如果模型訓練突然中斷訓練而歷史資訊又丟失了,那麼模型只能從頭訓練。這樣的深度學習框架就不具備“斷點訓練”的功能了,只有"重頭再來"的功能。現在的大型深度學習模型都需要很長的時間訓練,有的需要訓練好幾天,如果框架不提供斷點訓練的功能,一旦機器出現問題導致程式崩潰,模型就不得不重頭開始訓練,這會對工程師的身心造成巨大打擊……所以這個存檔機制極大地提高了模型訓練的可靠性。

從另一個方面考慮,如果模型訓練徹底結束,這些歷史資訊就變得無用了。caffemodel檔案需要儲存下來,而solverstate這個檔案可以被直接丟棄。因此這種分離儲存的方式特別方便操作。

從剛才提到的“斷點訓練”可以看出,深度學習其實包含了“再訓練”這個概念。一般來說“再訓練”包含兩種模式,其中一種就是上面提到的“斷點訓練”。從前面的配置檔案中可以看出,訓練的總迭代輪數是10000輪,每訓練5000輪,模型就會被儲存一次。如果模型在訓練的過程中被一些不可抗力打斷了(比方說機器斷電了),那麼大家可以從5000輪迭代時儲存的模型和歷史更新引數恢復出來,命令如下所示:

這裡不妨再深入一點分析。雖然模型的歷史更新資訊被儲存了下來,但當時的訓練場景真的被完全恢復了麼?似乎沒有,還有一個影響訓練的關鍵因素沒有恢復——資料,這個是不容易被訓練過程精確控制的。也就是說,首次訓練時第5001輪迭代訓練的資料和現在“斷點訓練”的資料是不一樣的。但是一般來說,只要保證每個訓練批次(batch)內資料的分佈相近,不會有太大的差異,兩種訓練都可以朝著正確的方向前進,其中存在的微小差距可以忽略不計。

第二種“再訓練”的方式則是有理論基礎支撐的訓練模式。這個模式會在之前訓練的基礎上,對模型結構做一定的修改,然後應用到其他的模型中。這種學習方式被稱作遷移學習(Transfer Learning)。這裡舉一個簡單的例子,在當前模型訓練完成之後,模型引數將被直接賦值到一個新的模型上,然後讓這個新模型重頭開始訓練。這個操作可以通過下面這個命令完成:

執行命令後Caffe會像往常一樣開始訓練並輸出大量日誌,但是在完成初始化之後,它會輸出這樣一條日誌:

這條日誌就是在告訴我們,當前的訓練是在這個路徑下的模型上進行"Finetune"。

4 訓練日誌分析

訓練過程中Caffe產生了大量的日誌,這些日誌包含很多訓練過程的資訊,非常很值得分析。分析的內容有很多,其中之一就是分析訓練過程中目標函式loss的變化曲線。在這個例子中,可以分析隨著迭代輪數不斷增加,Softmax Loss的變化情況。首先將訓練過程的日誌資訊儲存下來,比方說日誌資訊被儲存到mnist.log檔案中,然後用下面的命令可以將Iteration和Loss的資訊提取並儲存下來:

提取後的資訊可以用另一個指令碼完成Loss曲線的繪圖工作:

import matplotlib.pyplot as plt
x = []
y = []
with open('loss_data') as f:
    for line in f:
        sps = line[:-1].split()
        x.append(int(sps[0]))
        y.append(float(sps[1]))
plt.plot(x,y)
plt.show()

結果如圖1所示,可見Loss很快就降到了很低的地方,模型的訓練速度很快。這個優異的表現可以說明很多問題,但這裡就不做過多地分析了。

除此之外,日誌中輸出的其他資訊也可以被觀察分析,比方說測試環節的精確度等,它們也可以通過上面的方法解析出來。由於採用的方法基本相同,這裡有不去贅述了,各位可以自行嘗試。

正常訓練過程中,日誌裡只會顯示每一組迭代後模型訓練的整體資訊,如果想要了解更多詳細的資訊,就要將solver.prototxt中的除錯資訊開啟,這樣就可以獲得更多有用的資訊供大家分析:

debug_info:true

除錯資訊開啟後,每一組迭代後每一層網路的前向後向計算過程中的詳細資訊都可以被觀測到。這裡擷取其中一組迭代後的日誌資訊展示出來:

如果想要對網路的表現做更多地瞭解,那麼分析這些內容必不可少。

5 預測檢驗與分析

模型完成訓練後,就要對它的訓練表現做驗證,看看它在其他測試資料集上的正確性。Caffe提供了另外一個功能用於輸出測試的結果。以下就是它的指令碼:

指令碼的輸出結果如下所示:

除了完成測試的驗證,有時大家還需要知道模型更多的運算細節,這就需要深入模型內部去觀察模型產生的中間結果。使用Caffe提供的藉口,每一層網路輸出的中間結果都可以用視覺化的方法顯示出來,供大家觀測、分析模型每一層的作用。其中的程式碼如下所示:

執行上面的程式碼就可以生成如圖2到圖5這幾張影象,它們各代表一個模型層的輸出影象:

這一組圖展示了卷積神經網路是如何把一個數字轉變成特徵編碼的。這樣的方法雖然可以很好地看到模型內部的表現,比方說conv1的結果圖中有的提取了數字的邊界,有的明確了前景畫素所在的位置,這個現象和第3章中舉例的卷積效果有幾分相似。但是到了conv2的結果圖中,模型的輸出就變得讓人有些看不懂了。實際上想要真正看懂這些影象想表達的內容確實有些困難的。

6 效能測試

除了在測試資料上的準確率,模型的執行時間也非常值得關心。如果模型的執行時間太長,甚至到了不可用的程度,那麼即使它精度很高也沒有實際意義。測試時間的指令碼如下所示:

Caffe會正常的完成前向後向的計算,並記錄其中的時間。以下是使一次測試結果的時間記錄:

可以看出在效能測試的過程中,Lenet模型只需要不到1毫秒的時間就可以完成前向計算,這個速度還是很快的。當然這是在一個相對不錯的GPU上執行的,那麼如果在一個條件差的GPU上執行,結果如何呢?

可以看到不同的環境對於模型執行的時間影響很大。

以上就是模型訓練的一個完整過程。現在相信大家對深度學習模型的訓練和使用有了基本的瞭解。實際上看到這裡大家甚至可以扔下書去親自實踐不同模型的效果,開始深度學習的實戰之旅。

最後放一張Caffe原始碼的架構圖,以方便大家研究Caffe原始碼。

(點選放大影象)

答疑環節

Q1. Caffe和其他深度學習框度(如MxNet, Tensorflow等)比有什麼優缺點?

馮超:Caffe作為“老一代”的深度學習框架,從架構上來說有一個很大的不同之處,那就是它不是採用符號運算的方式進行設計的,這樣帶來了一些優點和缺點。

優點是Caffe在計算過程中,在記憶體使用和運算速度方面都有一定的優勢,記憶體使用較小,速度也比較有保證,相對而言,基於符號計算的框架在這兩方面會稍弱一些。

缺點是Caffe模型靈活性較弱。對於一些新出現的網路模型結構,Caffe適配起來需要一些技巧,需要一定的經驗,而MXNet,TensorFlow這樣的模型就簡單很多。

所以一般來說,MXNet、Tensorflow更適合實驗環境,Caffe比較適合工業界線上使用。其他方面,現在Caffe的社群相比Tensorflow可能要弱些,但依然是非常主流的開源框架。

Q2. 如何看待facebook新發布的caffe2深度學習框架?它比現有的caffe有什麼樣的改進?跟現在的TensorFlow相比如何?

馮超:caffe2最近比較熱,之前我沒有多看過,這兩天集中看了看,大概有以下一些感受:

  1. 有了Caffe2和Caffe相比變化大麼?prototxt和caffemodel海鬥可以用,基本上算向下相容,Caffe的一些知識技能還可以用。

  2. Caffe2的操作方式?從官方文件中看,Caffe2的python介面有了很大的進步。給出的示例主要是用python程式碼描述網路結構(和tf很像,也有checkpoint等概念)->生成記憶體版的網路模型->構建net和相關環境->執行程式碼,所以除了前面的python介面,後面的流程和caffe1很像。

  3. 內部架構上的變化?Caffe2借鑑了Tensorflow的一些概念,將過去的Net拆成兩部分:資料和網路圖。資料部分是Workspace,和tf的session很像,另外一部分是網路圖net。另外,Caffe2內部也將Layer改成了Op。

  4. 現在就上手Caffe2?幾乎有點早,目前官方給出的內容還比較少,大家也不必太心急。

  5. 最後說說Caffe2官方給出的一些亮點功能:跨平臺。這一次的Caffe2可以在各種不同的平臺上部署執行,這算是一個亮點。

Q3. caffe和pytorch都是facebook在推,那這兩個框架有什麼不同呢?

馮超:基本上實驗環境下主要用pytorch,線上環境用Caffe。

Q4. caffe有沒有提供一些preTraining的一些模型 方便更快地呼叫網路呢?

馮超:Caffe有自己的Model zoo,詳情可以上去看一看。

Q5. caffe在移動端實踐的實際案例嗎?

馮超:就我身邊有限的瞭解,一些公司做移動端嘗試還是使用了Tensorflow,並沒有用Caffe。

作者介紹

馮超,畢業於中國科學院大學,現就職於猿輔導公司,從事視覺與深度學習的應用研究工作。自2016年起在知乎開設了自己的專欄——《無痛的機器學習》,發表一些機器學習和深度學習的文章,收到了不錯的反響。

感謝杜小芳對本文的審校。

http://www.infoq.com/cn/articles/whole-process-of-caffe-depth-learning-training