Java呼叫R與Python

技術小能手發表於2018-07-19

1 為什麼我們要Java呼叫Python或R

Java是一門物件導向程式語言,不僅吸收了C++語言的各種優點,還摒棄了C++裡難以理解的多繼承、指標等概念,因此Java語言具有功能強大和簡單易用兩個特徵。Java語言作為靜態物件導向程式語言的代表,極好地實現了物件導向理論,允許程式設計師以優雅的思維方式進行復雜的程式設計 。Java具有簡單性、物件導向、分散式、健壯性、安全性、平臺獨立與可移植性、多執行緒、動態性等特點。Java可以編寫桌面應用程式、Web應用程式、分散式系統和嵌入式系統應用程式等;Python和R在資料科學中有較高的地位,能夠快速的實現特徵工程、資料探勘的建模。因此在生產中可能需要上線一些資料探勘模型時,此時軟體工程師(往往不懂資料科學)使用的工具Java和資料分析師的Python或R會有交集。

模型的線上化部署的方式有很多,比如我們可以把訓練的模型轉預測模型標記語言(PMML),這樣Java就可以直接呼叫PMML;也可以做成REST API,Java通過API介面與模型進行互動,以上這些R與Python都有成熟的解決辦法;最直接的辦法莫過於程式碼與程式碼的對接(code2code),我們可以通過Java直接呼叫開發好的R模型或Python模型,完成訓練,評價和預測的目的,下面我們會依次展示一個Java呼叫Python程式碼的例子和Java呼叫R的例子。

2.Java呼叫Python舉慄

首先我們知道Python和Java是可以相互呼叫的,而本節僅舉一個Java呼叫Python的栗子,Java呼叫Python的方式有很多,比如Jython(不過我不推薦大家使用,Jython版本更新緩慢,很多Python模組並沒有包含,在呼叫的過程中會出現各種問題),我推薦大家使用命令列的方式呼叫Python程式碼或指令碼,其核心方法為:

Runtime.getRuntime().exec(args1);

注意這種方式只能接收到python裡print的資料。所以如果你需要返回值,可以把返回值列印出來由Java接收就OK了,請後退,我要上程式碼了:

首先我們要有一個Python指令碼檔案,比如:

import sys
import pandas as pd
import numpy as np
import sklearn
import xgboost
import lightgbm
import tensorflow as tf
import keras

def my_test(str1,str2,str3,str4):
    return "Python函式執行:java調Python測試:"+str1+str2+str3+str4
    

if __name__=="__main__":
    print("指令碼名:", sys.argv[0])

    my_arg = []
    for i in range(0, len(sys.argv)):
        my_arg.append(sys.argv[i])
    print("Java傳入的引數長度為:"+str(len(my_arg)))
    
    result = my_test(my_arg[1],my_arg[2],my_arg[3],my_arg[4])
    print(result)

其次我們構造一個J2py類用來呼叫上述Python指令碼,並且實現Java資料與Python的互動(動態傳參的過程):

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @title J2py.java
 * @author 作者:XuJing
 * @date 建立時間:2018年7月13日下午2:39:05
 * @version 1.0.0
 * @parameter 引數及其意義:
 * @return 返回值:
 */

public class J2py {

    public static void main(String[] args) {
        // 需傳入的引數
        String a = "你好", b = "123", c = "徐靜", d = "qingdao";
        System.out.println("Java中動態引數已經初始化,準備傳參");
        // 設定命令列傳入引數
        String[] args1 = new String[] { "python","java\03_project\J2py\src\my_model.py", a,b, c, d }; 
        //Java資料a,b,c,d傳入Python
        Process pr;
        try {
            pr = Runtime.getRuntime().exec(args1); //最核心的函式

            BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream(), "gbk"));
            String line;
            List<String> lines = new ArrayList<String>();

            System.out.println("-----------------------------------------------");

            while ((line = in.readLine()) != null) {
                System.out.println(line);
                lines.add(line); //把Python的print值儲存了下來

            }
            System.out.println("-------------------------------------------------");

            in.close();

            pr.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Java調Python結束");

    }

}

在Eclipse下執行的結果如下:

Java中動態引數已經初始化,準備傳參
-----------------------------------------------
指令碼名: java 3_projectJ2pysrcmy_model.py
Java傳入的引數長度為:5
Python函式執行:java調Python測試:你好123徐靜qingdao
-------------------------------------------------
Java調Python結束

OK,成功了,虛線中間的部分就是我Python指令碼實現的功能,並且我們實現了Java資料與Python程式碼的引數傳遞。注意一點我在Python指令碼中匯入了部分我們常用到的模組,這種方式Java並沒有報錯,也就意味著我們均可以通過Java呼叫這些模組構造的模型,簡直是太棒了。

附:我的測試環境是Win7,Python3.6.4,JDK10.0.1,這裡需要說明的是我們也可以呼叫Python的虛擬環境,只是把Python的程式碼稍作修改就可以實現。

3.Java呼叫R舉慄

R與Java同樣可以進行相互呼叫,實際中很多R包都是由Java完成的,本節重點依然放在Java呼叫R程式碼或指令碼中,要注意的是Java呼叫R的方式也有很多,我們本節主要舉一個Java通過Rserve呼叫R程式碼或指令碼的例子,同時實現Python資料與R指令碼的互動。翠花,上程式碼:

首先我們要有一段R指令碼,在現實中可能是你用R開發好的模型,等待上線:

library(tidyverse)
library(ggplot2)
library(mlr)
library(xgboost)
library(tensorflow)
library(keras)

getSum <- function(x,y){
  m <- x + y
  print("成功執行了該R函式")  
  return(m)
}

其次,我們構造一個J2r的Java類,用來呼叫上面的R指令碼,並且實現資料互動和在Java中自動開啟Rserve:

import org.rosuda.REngine.REXP;
import org.rosuda.REngine.REXPMismatchException;
import org.rosuda.REngine.Rserve.RConnection;
import org.rosuda.REngine.Rserve.RserveException;

/**
 * @title J2r2.java
 * @author 作者:XuJing
 * @date 建立時間:2018年7月13日下午5:31:12
 * @version 1.0.0
 * @parameter 引數及其意義:
 * @return 返回值:
 */

public class J2r2 {

    public static void main(String[] args) {

        System.out.println(StartRserve.checkLocalRserve());

        System.out.println("準備開始Java呼叫R");
        System.out.println("-----------------------------------------------");
         RConnection rConnection = null;

         try {
         rConnection = new RConnection();
         rConnection.eval("source(`C:/test.R`)");

         } catch (RserveException e) {
         e.printStackTrace();
         } // 檔名不能帶中文,否則報錯:eval failed, request status: error code: 127
         int a = 2;
         int b = 3;
         int c = 4;
         int sum = 0;

         try {
         sum = rConnection.eval("getSum(" + a + "," + b + ")").asInteger();
         } catch (Exception e) {
         e.printStackTrace();
         }

         System.out.println("the sum = " + sum);
         rConnection.close();

        // 呼叫R程式碼
         System.out.println("呼叫R程式碼");

        RConnection rc = null;
        try {
            rc = new RConnection();
        } catch (RserveException e) {
            e.printStackTrace();
        }
        REXP x = null;
        try {
            x = rc.eval("library(xgboost);R.version.string");
        } catch (RserveException e) {
            e.printStackTrace();
        }
        try {
            System.out.println(x.asString());
        } catch (REXPMismatchException e) {
            e.printStackTrace();
        }
        rc.close();
        System.out.println("-----------------------------------------------");
        System.out.println("回到Java");

    }
}

在Eclipse下執行的結果如下:

準備開始Java呼叫R
-----------------------------------------------
the sum = 5
呼叫R程式碼
R version 3.4.0 (2017-04-21)
-----------------------------------------------
回到Java

OK成功。java同時可以呼叫第三方的R包,執行R指令碼傳參,並把資料返回給Java,這裡要注意的是需要匯入Rengine.jar和Rserve.jar兩個壓縮包,並且如果要實現Java中自動開啟Rserve需要在Rserve包中找到Rserve.java原始檔進行呼叫。

附:我的測試環境是Win7,R3.4.0,JDK10.0.1, 特別注意:不建議在Windows系統使用Rserve.

4.小結

通過以上兩種簡單方式我們就可以非常容易的通過Java呼叫R和Python封裝的資料探勘模型。實際上這是一種最簡單直接的模型線上化部署的辦法,在生產中我們要基於硬體和系統要求選擇合理的模型線上化部署的辦法。同時也可以直接使用Java或C等底層的語言開發自己的模型,減少不同程式碼間相互呼叫,提高模型的生產效率和執行速度。

原文釋出時間為:2018-07-17
本文作者: 徐靜
本文來自雲棲社群合作伙伴“Python愛好者社群”,瞭解相關資訊可以關注“Python愛好者社群


相關文章