人工智能-智能创意平台架构成长之路(一)--长篇开篇
人工智能-智能创意平台架构成长之路(二)--大数据架构篇
人工智能-智能创意平台架构成长之路(三)--机器学习算法工程服务化
人工智能-智能创意平台架构成长之路(四)-丰富多彩的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/*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… 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") }
未完待续...