正如《AI 之旅:啓程》一文所說,「機器學習做爲實現 AI 的主要手段,涉及到的知識和領域很是多,而 Google 提供的 TensorFlow 平臺讓普通人建立或訓練機器學習模型成爲可能,做爲普通人,你沒法提出革命性的機器學習理論,甚至沒法理解不少數學知識,可是有了 TensorFlow 平臺,有了一些訓練好的模型,你就能夠建立本身想要的,能幫助你本身或者幫助他人解決問題的模型」。TensorFlow 平臺提供了不少理論的具體實現,它們就像一個個功能良好的黑匣子,你不須要知道它背後的理論原理和數學公式多麼地複雜,你也不須要關心具體的實現細節,你只須要知道它能幫你完成哪些工做就能夠了java
TensorFlow 不但提供了電腦端的實現,還提供了針對移動端、Web 端、IoT 以及雲端等幾乎全部平臺的實現。因爲移動端、嵌入式設備、IoT 設備自己計算能力和存儲空間有限,因此對實現的要求更高,TensorFlow 提供了針對這些平臺專門精簡優化的 Lite 版本,叫 TensorFlow Lite,有了它,這些設備就能夠在本地(on-device)實現低延時低空間佔用的機器學習推理了。你可能會問,我能夠直接在後臺服務器(雲端)上實現模型的推理啊,這樣就不用考慮硬件的性能限制了,事實上的確能夠這樣,可是這也會帶來一些問題,如受到網絡傳輸速度的影響,從你發送推理網絡請求到你接收到請求結果的時間跨度可能會很長,你無法保證低延時,並且一旦數據離開了設備,你沒辦法保證隱私性和安全性,你也沒辦法保證設備總能聯網,並且網絡請求是一個特別耗電的行爲,因此你在選擇方案時須要慎重權衡一下
TensorFlow Lite 包含兩個主要組件,一個是解釋器(interpreter),能夠在不一樣的硬件類型(手機、嵌入式 Linux 設備和微控制器等)上運行專門優化的模型。一個是轉換器(converter),能夠將 TensorFlow 模型轉化成供解釋器使用的高效形式,以便優化空間佔用並提高性能
TensorFlow Lite 目前提供了 5 個訓練好(pre-trained)的模型:python
這 5 個模型你能夠直接使用它,也能夠經過遷移學習(transfer learning)的方式從新訓練成新的模型,可是若是你想使用其它訓練好的 TensorFlow 模型,你可能須要先轉換成 TensorFlow Lite 格式才能在 TensorFlow Lite 中使用它
因爲開發進度和水平的限制,目前 TensorFlow Lite 只支持 TensorFlow 操做(operations,簡稱 ops)的有限子集,若是發現有些操做不支持,能夠經過一些配置添加缺失的 TensorFlow opsgit
TensorFlow 的計算量通常很是大,傳統 CPU 的計算能力和傳統算法的計算能力很難知足需求,因此咱們須要針對 AI 算法特殊優化的硬件和軟件
從硬件層面上來講,GPU 的計算能力尤爲是浮點矩陣運算的能力比 CPU 要強大,因此能夠把這部分工做交給 GPU 去作以便加快推理速度提升效率,事實證實,這個速度的提高是很是可觀的, Pixel 3 手機上 MobileNet v1 圖像分類模型在開啓 GPU 加速後至少能快 5.5 倍,並且比 CPU 耗更少的電產生更少的熱量。固然,除了 GPU,像 NPU(Neural Networks Processing Unit)、TPU(Tensor Processing Unit)、DSP(Digital Signal Processor)等硬件也能勝任這些工做
從軟件層面來講,AI 相關的算法更加豐富,爲了從底層就支持 AI 算法,Google 從 Android 8.1(API level 27)開始新增了 NNAPI(Android Neural Networks API),一個爲了針對機器學習的計算密集型操做而專門設計的 C 語言 API,開發者通常經過機器學習框架間接使用這些 API,框架會利用 NNAPI 在支持的設備上執行硬件加速下的推理操做github
轉換器不可是用來把 TensorFlow 模型轉換成 TensorFlow Lite 格式(FlatBuffer)的工具,也是最多見的用來優化模型的工具。FlatBuffer 是一個高效的開源跨平臺序列化庫,相對於 protobuf(protocol buffers)來講更加簡單輕量。推薦在 Python API 中使用轉換器:算法
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
複製代碼
生成的 .tflite 文件就是供解釋器使用的模型,將它和標籤文件一同放到 assets 目錄下,爲了不被 AAPT 工具壓縮,須要添加配置項:
aaptOptions {
noCompress "tflite"
}
複製代碼
implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
複製代碼
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
複製代碼
推理的過程很簡單,就是先加載模型文件,而後用它實例化一個 Interpreter
解釋器,而後運行這個解釋器就能夠獲得結果了
建議預加載模型文件並完成內存映射以便加快加載時間同時減小內存髒頁:數組
private MappedByteBuffer loadModelFile(Activity activity) throws IOException {
AssetFileDescriptor fileDescriptor = activity.getAssets().openFd("mobilenet_v1_1.0_224.tflite");
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
FileChannel fileChannel = inputStream.getChannel();
long startOffset = fileDescriptor.getStartOffset();
long declaredLength = fileDescriptor.getDeclaredLength();
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
}
複製代碼
在構造解釋器時能夠經過類型爲 Interpreter.Options
的參數去配置這個解釋器,好比使用的線程數、使用 NNAPI、使用 GPU 加速等:安全
tfliteModel = loadModelFile(activity);
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(1);
tflite = new Interpreter(tfliteModel, options);
複製代碼
以圖像分類模型的簡單應用爲例,咱們的輸入是個 Bitmap 對象,輸出應該是包含標籤和對應機率的實體類列表:
服務器
public List<Recognition> recognizeImage(final Bitmap bitmap) {
...
}
複製代碼
public static class Recognition {
private final String id;
private final String title;
private final Float confidence;
private RectF location;
...
}
複製代碼
而爲了更高效地處理圖片數據,須要把 bitmap 轉成 ByteBuffer
格式,ByteBuffer
將圖片表示成每一個顏色通道 3 個字節的一維數組,咱們須要預分配這個內存並調用 order(ByteOrder.nativeOrder())
以保證每一位都以 native order 順序存儲:網絡
imgData =
ByteBuffer.allocateDirect(
DIM_BATCH_SIZE
* getImageSizeX()
* getImageSizeY()
* DIM_PIXEL_SIZE
* getNumBytesPerChannel());
imgData.order(ByteOrder.nativeOrder());
...
private void convertBitmapToByteBuffer(Bitmap bitmap) {
if (imgData == null) {
return;
}
imgData.rewind();
bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
int pixel = 0;
for (int i = 0; i < getImageSizeX(); ++i) {
for (int j = 0; j < getImageSizeY(); ++j) {
final int val = intValues[pixel++];
addPixelValue(val);
}
}
}
protected void addPixelValue(int pixelValue) {
imgData.putFloat((((pixelValue >> 16) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
imgData.putFloat((((pixelValue >> 8) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
imgData.putFloat(((pixelValue & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
}
複製代碼
而模型的輸出是個二維數組,表示預測的每一個標籤的機率:app
private List<String> labels;
private float[][] labelProbArray;
...
labels = loadLabelList(activity);
labelProbArray = new float[1][labels.size()];
...
private List<String> loadLabelList(Activity activity) throws IOException {
List<String> labels = new ArrayList<String>();
BufferedReader reader =
new BufferedReader(new InputStreamReader(activity.getAssets().open("labels.txt")));
String line;
while ((line = reader.readLine()) != null) {
labels.add(line);
}
reader.close();
return labels;
}
複製代碼
而後利用這個輸入輸出運行推理就好了:
public void runInference() {
tflite.run(imgData, labelProbArray);
}
複製代碼
最後將這個 labelProbArray 轉成 List<Recognition>
就能夠用於可視化輸出的顯示了
咱們發現 mobilenet_v1_1.0_224.tflite
模型文件的大小有 16.9 MB 這麼大,那咱們有沒有可能把它壓縮的更小呢?有,因爲模型使用了浮點的權重和激活函數,咱們就能夠經過量化(Quantization)的方式把模型壓縮至少 4 倍,量化有兩種方式,一種是訓練後量化(post-training quantization),不須要從新訓練模型,不過可能會有準確率的損失,若是這個損失超過了可接受的閾值,就只能使用另外一種方式,即量化訓練(quantized training)了。若是咱們選擇使用 mobilenet_v1_1.0_224_quant.tflite
模型文件,咱們會發現它只有 4.3 MB
另外一個比較重要的點是要學會權衡,有些模型雖然很複雜很大可是準確率很高,有些模型雖然準確率不是很高可是更小巧執行更快,你須要根據實際狀況選擇最適合的模型