人工智能-智能創意平台架構成長之路(三)--機器學習算法工程服務化
人工智能-智能創意平台架構成長之路(四)-豐富多彩的banner圖生成解密第一部分(對標阿里鹿班的設計)
我們接着 人工智能-智能創意平台架構成長之路(二)--大數據架構篇 繼續
前面我們講了很多都是創意平台應用層的設計,但是其實在人工智能中,最重要的是算法,關於算法的框架很多,這就會導致底層算法的實現語言也會非常多,我們最常用的語言是python,其次是C或者C++,還有go語言實現的算法,下表我們列舉了常用的機器學習框架以及他們支持的開發語言。
那么如何對這些語言實現的算法做工程化服務包裝呢?總不能提供一堆的算法函數給平台應用層去使用吧,而應用層平台一般都是java語言來實現的,那么應用層平台如何來跨語言調用算法呢?而且一般的研發隊伍中,都是java人員居多,那么java開發人員如何來把研究算法的博士們寫的算法函數給包裝成服務呢?
1、 python算法的服務化
針對這種情況,應該是比較簡單的,因為基於python的web框架非常多,我們很容易的就可以把python的算法代碼封裝為一個服務,最常用的框架有flask和Django,這里我們以flask為例,看一個示例代碼的實現。
# -*- coding: utf-8 -*- from flask import Flask, request, Response import json app = Flask(__name__) class Algorithm(object): … @app.route('/getSyncCrawlSjqqResult',methods = ['GET']) def getAlgorithm Result(): … return Response(json.dumps(Algorithm.parser(request.args.get("para"))),mimetype="application/json") if __name__ == '__main__': app.run(port=3001,host='0.0.0.0',threaded=True)
2、 C和C++的服務化
A、 使用java JNI 的接口方式調用C/C++,JNI是Java Native Interface的縮寫,通過使用 Java本地接口書寫程序,可以確保代碼在不同的平台上方便移植。 從Java1.1開始,JNI標准成為java平台的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他編程語言,只要調用約定受支持就可以了。使用java與本地已編譯的代碼交互,通常會喪失平台可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的。例如,使用一些舊的庫,與硬件、操作系統進行交互,或者為了提高程序的性能。安卓(Android)調用C/C++很多也是采用的這種方式,而且這種方式使得c或者C++的算法可以跑在storm或者spark上,OpenCV的java版本的包也是通過這種方式來實現,OpenCV的java庫是通過 java走jni的方式調用C++編寫的OpenCV,
java調用OpenCV的操作的步驟如下:
1)、編寫帶有native聲明的方法的java類
public class AlgorithmExample { public static native String callAlgorithm();//所有native關鍵詞修飾的都是對本地的聲明 static { System.loadLibrary("Algorithm.so");//載入本地算法庫 } public static void main(String[] args) { System.out.println(AlgorithmExample. callAlgorithm()) } }
在這段代碼中,最終的是在類初始化時,需要通過 System.loadLibrary("Algorithm.so")去加載算法的so庫包,這里的算法so庫包就是一個動態鏈接庫。
2)、java的代碼寫完后,我們就需要把java代碼編譯生成class文件
javac AlgorithmExample.java
3)、生成擴展名為h的頭文件,可以執行javah AlgorithmExample
jni HelloWorld 頭文件的內容:
/*DO NOT EDIT THIS FILE - it is machine generated*/ #include <jni.h> /*Header for class AlgorithmExample */ #ifndef _Included_ AlgorithmExample #define _Included_ AlgorithmExample #ifdef __cplusplus extern "C" { #endif /* *Class: AlgorithmExample *Method: callAlgorithm *Signature:()V */ JNIEXPORT String JNICALL Java_ AlgorithmExample_ callAlgorithm(JNIEnv*, jobject); #ifdef __cplusplus } #endif #endif
4)、 編寫本地方法實現和由javah命令生成的頭文件里面聲明的方法名相同的方法
#include "jni.h" #include " AlgorithmExample.h" //#include otherheaders JNIEXPORT String JNICALL Java_ AlgorithmExample_ callAlgorithm(JNIEnv *env, jobject obj) { printf("Helloworld!\n"); return ‘Helloworld’; }
5)、生成動態鏈接庫
gcc -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -Id:/java/include –Id:/java/include/win32 -shared -o (輸出的dll文件名,如AlgorithmExample.dll) (輸入的c/c++源文件,如abc.c)
6)、使用時,需要將生成的dll文件(windows環境下)或者so文件(linux文件下)放到項目的classpath目錄下。
B、C/C++回調java,也就是C/C++代碼也可以通過JNI的方式調用java代碼中的方法,但是這種使用方式不是很常見,具體使用方式可以參考博客園中的這篇文檔:https://www.cnblogs.com/jiangjh/p/10991365.html
不管是C/C++通過JNI的方式調用java 還是 java 通過JNI的方式調用C/C++,在實際情況中很容易出現一些問題,尤其是java通過JNI的方式調用C/C++。
l 內存泄露問題:
C/C++自身開辟的內存,JVM虛擬機的GC回收器無法幫助其自動回收,如果C/C++中沒有及時的free 內存,那么會造成內存泄露,而且這種內存泄露通過jmap獲取heap dump來查看內存使用快照時是看不到這塊的內存使用的。
l JNI自身存在的一些問題:
筆者就曾經遇到direct ByteBuffer內存無法回收,通過jni在虛擬機外內存中分配的direct ByteBuffer,在JVM的默認啟動時是沒有做大小限制的,direct ByteBuffer可以通過-XX:MaxDirectMemorySize來設置,此參數的含義是當Direct ByteBuffer分配的堆外內存到達指定大小后,即觸發Full GC。注意該值是有上限的,默認是64M,最大為sun.misc.VM.maxDirectMemory(),在程序中中可以獲得-XX:MaxDirectMemorySize的設置的值,而且這塊的direct ByteBuffer通過jmap無法查看該快內存的使用情況。也只能通過top來看它的內存使用情況。關於這塊可以參考筆者的另一篇博文:https://www.cnblogs.com/laoqing/p/10380536.html
我們再說一個安卓上人臉識別的算法例子,大部分的人臉識別的底層算法都是基於C或者C++來實現的,然后安卓上怎么用呢,我們都知道安卓提供了SDK,也提供了NDK。 NDK 可以用來編譯C或者C++的代碼,然后會生成so包,最后通過SDK,走java的JNI方式來調用so包。
C、通過python 調用C/C++,然后通過python來把算法封裝成服務
(作者的原創文章,轉載須注明出處。原創文章歸作者所有,歡迎轉載,但是保留版權。對於轉載了博主的原創文章,不標注出處的,作者將依法追究版權,請尊重作者的成果。請注明出處:https://www.cnblogs.com/laoqing/p/11364435.html)
Python中提供了ctypes,使用ctypes 可以很方便的調用C語言代碼,ctypes模塊提供了和C語言兼容的數據類型和函數來加載dll或so文件。
如下C的代碼中,提供了兩個函數,一個是兩個int類型的數相加,一個是兩個float類型的數相加,然后我們用python的ctypes模塊來調用這個代碼
#include <stdio.h> … int add_int(int, int); float add_float(float, float); int add_int(int numA, int numB) { return numA + numB; } float add_float(float numA, float numB) { return numA + numB; } …
使用gcc -shared -Wl,-soname,adder -o adderExample.so -fPIC addExample.c 來生成Linux下的so文件,將so包文件放到python 工程中。
然后我們就可以寫一段python代碼來調用了so包了
import ctypes adderExample = ctypes.cdll.LoadLibrary('./adderExample.so ') res_int = adderExample.add_int(10,5) print("10 + 5 等於 " + str(res_int)) a = ctypes.c_float(7.2) b = ctypes.c_float(5.3) add_float = adderExample.add_float add_float.restype = ctypes.c_float print("7.2 + 5.3 等於 " + str(add_float(a, b)))
使用ctypes會有很大的局限性,對於其他類似布爾型和浮點型這樣的類型,必須要使用正確的ctype類型才可以,但是調用C中的對象時,就很難做到。
由於python的解釋器本身就是用C語言來實現,那么其實只要我們用C寫的算法代碼能夠按照python解釋器的規范集成進去就可以。
D、通過C/C++語言自己來包裝服務
我們知道C和C++其實相對於Java來說,是更偏底層一點的語言,而且C是面向過程的語言,在很多公司的團隊中,基本都很少有C或者C++的開發人員,而且用C語言實現一個http 服務比用java或者python實現一個http服務其實有時候需要寫更多的代碼。但是C/C++肯定是可以用自己的語言來包裝實現服務。
3、Go的服務化
使用go語言來創建一個http服務,大致會有兩個過程,首先需要使用go來注冊一個路由,提供url模式和handler函數的映射。其次就是需要實例化一個server對象,並開啟對客戶端的請求監聽。
package example import ( "io" "net/http" "log" ) func main () { // 設置請求路由 http.HandleFunc("/algorithm", algorithm) // 路由做注冊,開啟監聽 err := http.ListenAndServe(":3000", nil) if err != nil { log.Fatal(err) } } func algorithm (response http.ResponseWriter, request *http.Request) { io.WriteString(response, "this is algorithm") }
未完待續...