- 移動(dòng)深度學(xué)習(xí)
- 李永會(huì)
- 2311字
- 2019-12-05 14:22:36
1.8.4 開(kāi)發(fā)一個(gè)基于移動(dòng)端深度學(xué)習(xí)框架的Android App
在開(kāi)始創(chuàng)建Android App之前,需要下載并安裝Android Studio 3或以上版本。由于新版本的Android Studio已經(jīng)默認(rèn)安裝了Android SDK,所以整個(gè)過(guò)程會(huì)比較方便(如果涉及對(duì)Google的訪問(wèn),可能需要配置代理)。
安裝Android Studio以后,創(chuàng)建一個(gè)新項(xiàng)目,名稱(chēng)自擬即可。由于Kotlin語(yǔ)言簡(jiǎn)單明了,所以為了快速成型,筆者在這選擇了Kotlin。不論是Java還是Kotlin,都不會(huì)影響工程的創(chuàng)建和開(kāi)發(fā),只要選擇你認(rèn)為最容易實(shí)現(xiàn)的語(yǔ)言就可以。
本節(jié)使用的源碼見(jiàn)“鏈接6”。雖然可以復(fù)制源碼并直接運(yùn)行,但仍然建議參照源碼從零開(kāi)始搭建并編寫(xiě)這一部分代碼,這樣可以更深刻地理解一個(gè)簡(jiǎn)單的視覺(jué)神經(jīng)網(wǎng)絡(luò)程序在移動(dòng)端運(yùn)行的步驟。
有了IDE等基本環(huán)境后,建立一個(gè)基礎(chǔ)工程,如圖1-19所示:

圖1-19 在Android平臺(tái)創(chuàng)建基礎(chǔ)工程
在GitHub上的Paddle-Lite項(xiàng)目中可以找到一些測(cè)試模型下載地址,截至2019年8月, GitHub社區(qū)使用的模型見(jiàn)“鏈接7”。這些測(cè)試模型可以用于開(kāi)發(fā)相關(guān)程序。
將模型包下載到本地并解壓,就能得到一系列測(cè)試模型。在本例中,筆者使用的模型是MobileNet,從模型文件中可以看到這個(gè)模型的基本構(gòu)成、卷積的尺寸和步長(zhǎng)等。
接下來(lái)將準(zhǔn)備使用的模型目錄拷貝到工程中。我們將MobileNet目錄的內(nèi)容放到assets/pml_demo下,以工程的src目錄為根目錄,展開(kāi)層級(jí)如下:
src └── main
├── AndroidManifest.xml ├── assets │ └── pml_demo │ ├── apple.jpg │ ├── banana.jpeg │ ├── hand.jpg │ ├── hand2.jpg │ └── mobilenet │ ├── __model__ │ ├── conv1_biases │ ├── conv1_bn_mean │ ├── conv1_bn_offset │ ├── conv1_bn_scale │ ├── conv1_bn_variance │ ├── conv1_weights │ ├── conv2_1_dw_biases
將模型拷貝到目地位置后,就要開(kāi)始開(kāi)發(fā)App的相關(guān)功能了,圖1-20所示是筆者的App工程布局。

圖1-20 App工程布局示例
App啟動(dòng)后的第一件事是將模型文件從磁盤(pán)加載到內(nèi)存中,這個(gè)過(guò)程被封裝在ModelLoader中。在MainActivity中實(shí)現(xiàn)init初始化方法,在初始化過(guò)程中加載模型。經(jīng)過(guò)簡(jiǎn)化后的代碼如下所示(如需運(yùn)行完整代碼,請(qǐng)見(jiàn)“鏈接8”)。
privatefun init() { updateCurrentModel() mModelLoader.setThreadCount(mThreadCounts) thread_counts.text = "$mThreadCounts" clearInfos() mCurrentPath = banana.absolutePath predict_banada.setOnClickListener { scaleImageAndPredictImage(mCurrentPath, mPredictCounts) } btn_takephoto.setOnClickListener { if(! isHasSdCard) { Toast.makeText(this@MainActivity, R.string.sdcard_not_available, Toast.LENGTH_LONG).show() return@setOnClickListener } takePicFromCamera() } bt_load.setOnClickListener { isloaded = true mModelLoader.load() } bt_clear.setOnClickListener { isloaded = false mModelLoader.clear() clearInfos() } ll_model.setOnClickListener { MaterialDialog.Builder(this) .title("選擇模型") .items(modelList) .itemsCallbackSingleChoice(modelList.indexOf(mCurrentType)) { _, _, which, text -> info { "which=$which" } info { "text=$text" }
mCurrentType = modelList[which] updateCurrentModel() reloadModel() clearInfos() true } .positiveText("確定") .show() } ll_threadcount.setOnClickListener { MaterialDialog.Builder(this) .title("設(shè)置線程數(shù)量") .items(threadCountList) .itemsCallbackSingleChoice(threadCountList.indexOf(mThreadCo unts)) { _, _, which, _ -> mThreadCounts = threadCountList[which] info { "mThreadCounts=$mThreadCounts" } mModelLoader.setThreadCount(mThreadCounts) reloadModel() thread_counts.text = "$mThreadCounts" clearInfos() true } .positiveText("確定") .show() } runcount_counts.text = "$mPredictCounts" ll_runcount.setOnClickListener { MaterialDialog.Builder(this) .inputType(InputType.TYPE_CLASS_NUMBER) .input("設(shè)置預(yù)測(cè)次數(shù)", "10") { _, input -> mPredictCounts = input.toString().toLong() info { "mRunCount=$mPredictCounts" } mModelLoader.mTimes = mPredictCounts reloadModel() runcount_counts.text = "$mPredictCounts" }.inputRange(1, 3)
.show() } }
MainActivity類(lèi)的代碼也從側(cè)面反映了一個(gè)視覺(jué)深度學(xué)習(xí)App需要處理的一些問(wèn)題,比如與圖像相關(guān)的權(quán)限、輸入尺寸等問(wèn)題,可以從初始化等核心方法入手。從上面代碼中能看到MainActivity類(lèi)中的init方法實(shí)現(xiàn),init方法邏輯包含Loader的初始處理和一些基本事件的監(jiān)聽(tīng)。由于深度學(xué)習(xí)技術(shù)對(duì)算力要求較高,所以往往會(huì)利用多線程處理技術(shù)來(lái)提升性能,這里的init方法就調(diào)用了多線程處理過(guò)程。多線程相關(guān)的底層實(shí)現(xiàn)使用了openmp api,多線程邏輯相對(duì)簡(jiǎn)單地作為入口參數(shù)傳入其中。
MainActivity作為界面和調(diào)起入口角色,除了要負(fù)責(zé)init初始化任務(wù),還要負(fù)責(zé)調(diào)起邏輯。下面就是其調(diào)起預(yù)處理和深度學(xué)習(xí)預(yù)測(cè)過(guò)程的代碼。
/** * 縮放,然后預(yù)測(cè)這張圖片 */ private funscaleImageAndPredictImage(path: String? , times: Long) { if(path == null) { Toast.makeText(this, "圖片lost", Toast.LENGTH_SHORT).show() return } if(mModelLoader.isbusy) { Toast.makeText(this, "處于前一次操作中", Toast.LENGTH_SHORT).show() return } mModelLoader.clearTimeList() tv_infos.text = "預(yù)處理數(shù)據(jù),執(zhí)行運(yùn)算..." mModelLoader.predictTimes(times) Observable .just(path) .map { if(! isloaded) { isloaded = true mModelLoader.setThreadCount(mThreadCounts) mModelLoader.load() } mModelLoader.getScaleBitmap( this@MainActivity, path
) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnNext { bitmap -> show_image.setImageBitmap(bitmap) } .map { bitmap -> varfloatsTen: FloatArray? = null for(i in0..(times - 1)) { valfloats = mModelLoader.predictImage(bitmap) valpredictImageTime = mModelLoader.predictImageTime mModelLoader.timeList.add(predictImageTime) if(i == times / 2) { floatsTen = floats } } Pair(floatsTen! ! , bitmap) } .observeOn(AndroidSchedulers.mainThread()) .map { floatArrayBitmapPair -> mModelLoader.mixResult(show_image, floatArrayBitmapPair) floatArrayBitmapPair.second floatArrayBitmapPair.first } .observeOn(Schedulers.io()) .map(mModelLoader::processInfo) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object: Observer<String? > { override funonSubscribe(d: Disposable) { mModelLoader.isbusy = true } override funonNext(resultInfo: String) { tv_infomain.text = mModelLoader.getMainMsg() tv_preinfos.text = mModelLoader.getDebugInfo() + "\n" + mModelLoader.timeInfo + "\n" + "點(diǎn)擊查看結(jié)果" tv_preinfos.setOnClickListener { MaterialDialog.Builder(this@MainActivity)
.title("結(jié)果:") .content(resultInfo) .show() } } override funonComplete() { mModelLoader.isbusy = false tv_infos.text = "" } override funonError(e: Throwable) { mModelLoader.isbusy = false } }) }
多數(shù)情況下,深度學(xué)習(xí)程序要有預(yù)處理過(guò)程,目的是將輸入尺寸和格式規(guī)則化,視覺(jué)深度學(xué)習(xí)的處理過(guò)程也不例外。如果不是可變輸入的網(wǎng)絡(luò)結(jié)構(gòu),那么一張輸入圖片在進(jìn)入神經(jīng)網(wǎng)絡(luò)計(jì)算之前需要經(jīng)歷一些“整形”,這樣能讓輸入尺寸符合預(yù)期。下面來(lái)看一下包含主要計(jì)算邏輯的Loader,它包含預(yù)處理、預(yù)測(cè)等邏輯的直接實(shí)現(xiàn)。圖像本身的數(shù)據(jù)是一個(gè)矩陣,因而預(yù)處理邏輯往往也是以矩陣的方式來(lái)處理的。
override fungetScaledMatrix(bitmap: Bitmap, desWidth: Int, desHeight: Int): FloatArray { valrsGsBs = getRsGsBs(bitmap, desWidth, desHeight) valrs = rsGsBs.first valgs = rsGsBs.second valbs = rsGsBs.third valdataBuf = FloatArray(3 * desWidth * desHeight) if(rs.size + gs.size + bs.size ! = dataBuf.size) { throwIllegalArgumentException("rs.size + gs.size + bs.size ! = dataBuf. size should equal") } // bbbb... gggg.... rrrr... for(i indataBuf.indices) {
dataBuf[i] = when{ i < bs.size -> (bs[i] - means[0]) * scale i < bs.size + gs.size -> (gs[i - bs.size] - means[1]) * scale else-> (rs[i - bs.size - gs.size] - means[2]) * scale } } returndataBuf }
從上面的代碼也能看到,這個(gè)預(yù)處理過(guò)程結(jié)束后得到的是一個(gè)BGR(藍(lán)、綠、紅)格式的數(shù)組。這部分代碼在MobileNetModelLoaderImpl類(lèi)中可以找到(完整代碼見(jiàn)“鏈接9”)。
前面編譯了Paddle-Lite的so庫(kù),它是使用C++編寫(xiě)的工程?,F(xiàn)在我們要在Android App中使用相關(guān)so庫(kù)中的功能,需要通過(guò)JNI(Java Native Interface)調(diào)用Paddle-Lite庫(kù)函數(shù),將數(shù)據(jù)從Kotlin層傳入JNI,得到預(yù)測(cè)結(jié)構(gòu),如下面的代碼所示。
override funpredictImage(inputBuf: FloatArray): FloatArray? { varpredictImage: FloatArray? = null try{ valstart = System.currentTimeMillis() predictImage = PML.predictImage(inputBuf, ddims) valend = System.currentTimeMillis() predictImageTime = end - start } catch(e: Exception) { } returnpredictImage } override funpredictImage(bitmap: Bitmap): FloatArray? { returnpredictImage(getScaledMatrix(bitmap, getInputSize(), getInputSize())) }
從上述代碼可以看出,如果基于Paddle-Lite使用層面編寫(xiě)深度學(xué)習(xí)App,那么思路并不復(fù)雜。從MobileNetModelLoaderImpl類(lèi)中可以看到,核心調(diào)用過(guò)程的代碼量也非常少。
上述代碼省略了文件拷貝和其他一些預(yù)處理過(guò)程,只展示了核心處理過(guò)程。從中可以看到,使用已有的深度學(xué)習(xí)庫(kù)集成并開(kāi)發(fā)深度學(xué)習(xí)功能是比較簡(jiǎn)單的。源代碼在GitHub相應(yīng)庫(kù)中(見(jiàn)“鏈接10”),使用Android Stuido直接運(yùn)行,就能看到圖1-21所示的效果,Demo App對(duì)香蕉圖片正確分類(lèi),并輸出了相應(yīng)的文本。

圖1-21 使用Paddle-Lite框架實(shí)現(xiàn)的Demo運(yùn)行效果
- Windows XP中文版應(yīng)用基礎(chǔ)
- Zabbix Network Monitoring(Second Edition)
- 21天學(xué)通ASP.NET
- RPA:流程自動(dòng)化引領(lǐng)數(shù)字勞動(dòng)力革命
- Supervised Machine Learning with Python
- Moodle Course Design Best Practices
- 單片機(jī)技術(shù)一學(xué)就會(huì)
- 激光選區(qū)熔化3D打印技術(shù)
- 機(jī)器人人工智能
- Photoshop CS5圖像處理入門(mén)、進(jìn)階與提高
- 生物3D打?。簭尼t(yī)療輔具制造到細(xì)胞打印
- 數(shù)字多媒體技術(shù)基礎(chǔ)
- 機(jī)器學(xué)習(xí)案例分析(基于Python語(yǔ)言)
- 案例解說(shuō)Delphi典型控制應(yīng)用
- Windows 7來(lái)了