mxnet:結合R與GPU加速深度學習(轉)


近年來,深度學習可謂是機器學習方向的明星概念,不同的模型分別在圖像處理與自然語言處理等任務中取得了前所未有的好成績。在實際的應用中,大家除了關心模型的准確度,還常常希望能比較快速地完成模型的訓練。一個常用的加速手段便是將模型放在GPU上進行訓練。然而由於種種原因,R語言似乎缺少一個能夠在GPU上訓練深度學習模型的程序包。

DMLC(Distributed (Deep) Machine Learning Community)是由一群極客發起的組織,主要目標是提供快速高質量的開源機器學習工具。近來流行的boosting模型xgboost便是出自這個組織。最近DMLC開源了一個深度學習工具mxnet,這個工具含有R,python,julia等語言的接口。本文以R接口為主,向大家介紹這個工具的性能與使用方法。

一、五分鍾入門指南

在這一節里,我們在一個樣例數據上介紹mxnet的基本使用方法。目前mxnet還沒有登錄CRAN的計划,所以安裝方法要稍微復雜一些。

  • 如果你是Windows/Mac用戶,那么可以通過下面的代碼安裝預編譯的版本。這個版本會每周進行預編譯,不過為了保證兼容性,只能使用CPU訓練模型。
install.packages("drat", repos="https://cran.rstudio.com") drat:::addRepo("dmlc") install.packages("mxnet")
  • 如果你是Linux用戶或者想嘗試GPU版本,請參考這個鏈接里的詳細編譯教程在本地進行編譯。

安裝完畢之后,我們就可以開始訓練模型了,下面兩個小節分別介紹兩種不同的訓練神經網絡的方法。

二分類模型與mx.mlp

首先,我們准備一份數據,並進行簡單的預處理:

require(mlbench) require(mxnet) data(Sonar, package="mlbench") Sonar[,61] = as.numeric(Sonar[,61])-1 train.ind = c(1:50, 100:150) train.x = data.matrix(Sonar[train.ind, 1:60]) train.y = Sonar[train.ind, 61] test.x = data.matrix(Sonar[-train.ind, 1:60]) test.y = Sonar[-train.ind, 61]

我們借用mlbench包中的一個二分類數據,並且將它分成訓練集和測試集。mxnet提供了一個訓練多層神經網絡的函數mx.mlp,我們額可以通過它來訓練一個神經網絡模型。下面是mx.mlp中的部分參數:

  • 訓練數據與預測變量
  • 每個隱藏層的大小
  • 輸出層的結點數
  • 激活函數類型
  • 損失函數類型
  • 進行訓練的硬件(CPU還是GPU)
  • 其他傳給mx.model.FeedForward.create的高級參數

了解了大致參數后,我們就可以理解並讓R運行下面的代碼進行訓練了。

mx.set.seed(0) model <- mx.mlp(train.x, train.y, hidden_node=10, out_node=2, out_activation="softmax", num.round=20, array.batch.size=15, learning.rate=0.07, momentum=0.9, eval.metric=mx.metric.accuracy)
## Auto detect layout of input matrix, use rowmajor.. ## Start training with 1 devices ## [1] Train-accuracy=0.488888888888889 ## [2] Train-accuracy=0.514285714285714 ## [3] Train-accuracy=0.514285714285714 ... ## [18] Train-accuracy=0.838095238095238 ## [19] Train-accuracy=0.838095238095238 ## [20] Train-accuracy=0.838095238095238

這里要注意使用mx.set.seed而不是R自帶的set.seed函數來控制隨機數。因為mxnet的訓練過程可能會運行在不同的運算硬件上,我們需要一個足夠快的隨機數生成器來管理整個隨機數生成的過程。模型訓練好之后,我們可以很簡單地進行預測:

preds = predict(model, test.x) 
## Auto detect layout of input matrix, use rowmajor..
pred.label = max.col(t(preds))-1 table(pred.label, test.y) 
##           test.y
## pred.label  0  1
##          0 24 14
##          1 36 33

如果進行的是多分類預測,mxnet的輸出格式是類數X樣本數。

回歸模型與自定義神經網絡

mx.mlp接口固然很方便,但是神經網絡的一大特點便是它的靈活性,不同的結構可能有着完全不同的特性。mxnet的亮點之一便是它賦予了用戶極大的自由度,從而可以任意定義需要的神經網絡結構。我們在這一節用一個簡單的回歸任務介紹相關的語法。

首先,我們仍然要准備好一份數據。

data(BostonHousing, package="mlbench") train.ind = seq(1, 506, 3) train.x = data.matrix(BostonHousing[train.ind, -14]) train.y = BostonHousing[train.ind, 14] test.x = data.matrix(BostonHousing[-train.ind, -14]) test.y = BostonHousing[-train.ind, 14]

mxnet提供了一個叫做“Symbol”的系統,從而使我們可以定義結點之間的連接方式與激活函數等參數。下面是一個定義沒有隱藏層神經網絡的簡單例子:

# 定義輸入數據 data <- mx.symbol.Variable("data") # 完整連接的隱藏層 # data: 輸入源 # num_hidden: 該層的節點數 fc1 <- mx.symbol.FullyConnected(data, num_hidden=1) # 針對回歸任務,定義損失函數 lro <- mx.symbol.LinearRegressionOutput(fc1)

在神經網絡中,回歸與分類的差別主要在於輸出層的損失函數。這里我們使用了平方誤差來訓練模型。希望能更進一步了解Symbol的讀者可以繼續閱讀這兩份分別以代碼配圖為主的文檔。

定義了神經網絡之后,我們便可以使用mx.model.FeedForward.create進行訓練了。

mx.set.seed(0) model <- mx.model.FeedForward.create(lro, X=train.x, y=train.y, ctx=mx.cpu(), num.round=50, array.batch.size=20, learning.rate=2e-6, momentum=0.9, eval.metric=mx.metric.rmse) 
## Auto detect layout of input matrix, use rowmajor..
## Start training with 1 devices
## [1] Train-rmse=16.063282524034
## [2] Train-rmse=12.2792375712573
## [3] Train-rmse=11.1984634005885

...

## [48] Train-rmse=8.26890902770415
## [49] Train-rmse=8.25728089053853
## [50] Train-rmse=8.24580511500735

這里我們還針對回歸任務修改了eval.metric參數。目前我們提供的評價函數包括”accuracy”,”rmse”,”mae” 和 “rmsle”,用戶也可以針對需要自定義評價函數,例如:

demo.metric.mae <- mx.metric.custom("mae", function(label, pred) { res <- mean(abs(label-pred)) return(res) })
mx.set.seed(0) model <- mx.model.FeedForward.create(lro, X=train.x, y=train.y, ctx=mx.cpu(), num.round=50, array.batch.size=20, learning.rate=2e-6, momentum=0.9, eval.metric=demo.metric.mae) 
## Auto detect layout of input matrix, use rowmajor..
## Start training with 1 devices
## [1] Train-mae=13.1889538083225
## [2] Train-mae=9.81431959337658
## [3] Train-mae=9.21576419870059

...

## [48] Train-mae=6.41731406417158
## [49] Train-mae=6.41011292926139
## [50] Train-mae=6.40312503493494

至此,你已經掌握了基本的mxnet使用方法。接下來,我們將介紹更好玩的應用。

二、手寫數字競賽

在這一節里,我們以Kaggle上的手寫數字數據集(MNIST)競賽為例子,介紹如何通過mxnet定義一個強大的神經網絡,並在GPU上快速訓練模型。

第一步,我們從Kaggle上下載數據,並將它們放入data/文件夾中。然后我們讀入數據,並做一些預處理工作。

require(mxnet)
train <- read.csv('data/train.csv', header=TRUE) test <- read.csv('data/test.csv', header=TRUE) train <- data.matrix(train) test <- data.matrix(test) train.x <- train[,-1] train.y <- train[,1] train.x <- t(train.x/255) test <- t(test/255)

最后兩行預處理的作用有兩個:

  • 原始灰度圖片數值處在[0,255]之間,我們將其變換到[0,1]之間。
  • mxnet接受 像素X圖片 的輸入格式,所以我們對輸入矩陣進行了轉置。

接下來我們定義一個特別的神經網絡結構:LeNet。這是Yann LeCun提出用於識別手寫數字的結構,也是最早的卷積神經網絡之一。同樣的,我們使用Symbol語法來定義,不過這次結構會比較復雜。

# input data <- mx.symbol.Variable('data') # first conv conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=20) tanh1 <- mx.symbol.Activation(data=conv1, act_type="tanh") pool1 <- mx.symbol.Pooling(data=tanh1, pool_type="max", kernel=c(2,2), stride=c(2,2)) # second conv conv2 <- mx.symbol.Convolution(data=pool1, kernel=c(5,5), num_filter=50) tanh2 <- mx.symbol.Activation(data=conv2, act_type="tanh") pool2 <- mx.symbol.Pooling(data=tanh2, pool_type="max", kernel=c(2,2), stride=c(2,2)) # first fullc flatten <- mx.symbol.Flatten(data=pool2) fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=500) tanh3 <- mx.symbol.Activation(data=fc1, act_type="tanh") # second fullc fc2 <- mx.symbol.FullyConnected(data=tanh3, num_hidden=10) # loss lenet <- mx.symbol.SoftmaxOutput(data=fc2)

為了讓輸入數據的格式能對應LeNet,我們要將數據變成R中的array格式:

train.array <- train.x dim(train.array) <- c(28, 28, 1, ncol(train.x)) test.array <- test dim(test.array) <- c(28, 28, 1, ncol(test))

接下來我們將要分別使用CPU和GPU來訓練這個模型,從而展現不同的訓練效率。

n.gpu <- 1 device.cpu <- mx.cpu() device.gpu <- lapply(0:(n.gpu-1), function(i) { mx.gpu(i) })

我們可以將GPU的每個核以list的格式傳遞進去,如果有BLAS等自帶矩陣運算並行的庫存在,則沒必要對CPU這么做了。

我們先在CPU上進行訓練,這次我們只進行一次迭代:

mx.set.seed(0) tic <- proc.time() model <- mx.model.FeedForward.create(lenet, X=train.array, y=train.y, ctx=device.cpu, num.round=1, array.batch.size=100, learning.rate=0.05, momentum=0.9, wd=0.00001, eval.metric=mx.metric.accuracy, epoch.end.callback=mx.callback.log.train.metric(100)) 
## Start training with 1 devices
## Batch [100] Train-accuracy=0.1066
## Batch [200] Train-accuracy=0.16495
## Batch [300] Train-accuracy=0.401766666666667
## Batch [400] Train-accuracy=0.537675
## [1] Train-accuracy=0.557136038186157
print(proc.time() - tic) 
##    user  system elapsed
## 130.030 204.976  83.821

在CPU上訓練一次迭代一共花了83秒。接下來我們在GPU上訓練5次迭代:

mx.set.seed(0) tic <- proc.time() model <- mx.model.FeedForward.create(lenet, X=train.array, y=train.y, ctx=device.gpu, num.round=5, array.batch.size=100, learning.rate=0.05, momentum=0.9, wd=0.00001, eval.metric=mx.metric.accuracy, epoch.end.callback=mx.callback.log.train.metric(100)) 
## Start training with 1 devices
## Batch [100] Train-accuracy=0.1066
## Batch [200] Train-accuracy=0.1596
## Batch [300] Train-accuracy=0.3983
## Batch [400] Train-accuracy=0.533975
## [1] Train-accuracy=0.553532219570405
## Batch [100] Train-accuracy=0.958
## Batch [200] Train-accuracy=0.96155
## Batch [300] Train-accuracy=0.966100000000001
## Batch [400] Train-accuracy=0.968550000000003
## [2] Train-accuracy=0.969071428571432
## Batch [100] Train-accuracy=0.977
## Batch [200] Train-accuracy=0.97715
## Batch [300] Train-accuracy=0.979566666666668
## Batch [400] Train-accuracy=0.980900000000003
## [3] Train-accuracy=0.981309523809527
## Batch [100] Train-accuracy=0.9853
## Batch [200] Train-accuracy=0.985899999999999
## Batch [300] Train-accuracy=0.986966666666668
## Batch [400] Train-accuracy=0.988150000000002
## [4] Train-accuracy=0.988452380952384
## Batch [100] Train-accuracy=0.990199999999999
## Batch [200] Train-accuracy=0.98995
## Batch [300] Train-accuracy=0.990600000000001
## Batch [400] Train-accuracy=0.991325000000002
## [5] Train-accuracy=0.991523809523812
print(proc.time() - tic) 
##    user  system elapsed
##   9.288   1.680   6.889

在GPU上訓練5輪迭代只花了不到7秒,快了數十倍!可以看出,對於這樣的網絡結構,GPU的加速效果是非常顯著的。有了快速訓練的辦法,我們便可以很快的做預測,並且提交到Kaggle上了:

preds <- predict(model, test.array) pred.label <- max.col(t(preds)) - 1 submission <- data.frame(ImageId=1:ncol(test), Label=pred.label) write.csv(submission, file='submission.csv', row.names=FALSE, quote=FALSE)

三、圖像識別應用

其實對於神經網絡當前的應用場景而言,識別手寫數字已經不夠看了。早些時候,Google公開了一個雲API,讓用戶能夠檢測一幅圖像里面的內容。現在我們提供一個教程,讓大家能夠自制一個圖像識別的在線應用。

DMLC用在ImageNet數據集上訓練了一個模型,能夠直接拿來對真實圖片進行分類。同時,我們搭建了一個Shiny應用,只需要不超過150行R代碼就能夠自己在瀏覽器中進行圖像中的物體識別。

為了搭建這個應用,我們要安裝shiny和imager兩個R包:

install.packages("shiny", repos="https://cran.rstudio.com") install.packages("imager", repos="https://cran.rstudio.com")

現在你已經配置好了mxnet, shiny和imager三個R包,最困難的部分已經完成了!下一步則是讓shiny直接下載並運行我們准備好的代碼:

shiny::runGitHub("thirdwing/mxnet_shiny")

第一次運行這個命令會花上幾分鍾時間下載預先訓練好的模型。訓練的模型是Inception-BatchNorm Network,如果讀者對它感興趣,可以閱讀這篇文章。准備就緒之后,你的瀏覽器中會出現一個網頁應用,就用本地或在線圖片來挑戰它吧!

如果你只需要一個圖像識別的模塊,那么我們下面給出最簡單的一段R代碼讓你能進行圖像識別。首先,我們要導入預訓練過的模型文件:

model <<- mx.model.load("Inception/Inception_BN", iteration = 39) synsets <<- readLines("Inception/synset.txt") mean.img <<- as.array(mx.nd.load("Inception/mean_224.nd")[["mean_img"]])

接下來我們使用一個函數對圖像進行預處理,這個步驟對於神經網絡模型而言至關重要。

preproc.image <- function(im, mean.image) { # crop the image shape <- dim(im) short.edge <- min(shape[1:2]) yy <- floor((shape[1] - short.edge) / 2) + 1 yend <- yy + short.edge - 1 xx <- floor((shape[2] - short.edge) / 2) + 1 xend <- xx + short.edge - 1 croped <- im[yy:yend, xx:xend,,] # resize to 224 x 224, needed by input of the model. resized <- resize(croped, 224, 224) # convert to array (x, y, channel) arr <- as.array(resized) dim(arr) = c(224, 224, 3) # substract the mean normed <- arr - mean.img # Reshape to format needed by mxnet (width, height, channel, num) dim(normed) <- c(224, 224, 3, 1) return(normed) }

最后我們讀入圖像,預處理與預測就可以了。

im <- load.image(src) normed <- preproc.image(im, mean.img) prob <- predict(model, X = normed) max.idx <- order(prob[,1], decreasing = TRUE)[1:5] result <- synsets[max.idx]

四、參考資料

MXNet是一個在底層與接口都有着豐富功能的軟件,如果讀者對它感興趣,可以參考一些額外的材料來進一步了解MXNet,或者是深度學習這個領域。

轉自:http://cos.name/tag/xgboost/


免責聲明!

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



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