PMML模型檔案在機器學習的實踐經驗
這種方案,在本次參加 QCon 大會時,Paypal的機器學習平臺中也有所提及:
PMML
預測模型標記語言(Predictive Model Markup Language,PMML)是一種可以呈現預測分析模型的事實標準語言。標準東西的好處就是,各種開發語言都可以使用相應的包,把模型檔案轉成這種中間格式,而另外一種開發語言,可以使用相應的包匯入該檔案做線上預測。
不過,當訓練和預測使用同一種開發語言的時候,PMML 就沒有必要使用了,因為任何中間格式都會犧牲掉獨有的優化。本文介紹的內容是採用Python語言做模型訓練,線上採用 Java 載入模型做預測。在模型訓練端,分別介紹了 python sk-learn 和 xgboost 訓練模型。
正式開始之前,首先總結下模型訓練和線上預測的流程圖
離線部分負責模型訓練和匯出模型,線上匯入模型並且做預測。當然特徵工程部分主要做特徵變換,例如 分桶,單值編碼,歸一化等。
SK-Learn
該開源專案支援 sk-learn模型轉成PMML,專案地址:jpmml/sklearn2pmml,擴充了一個例子定義如何使用sk-learn匯出帶有特徵工程的模型檔案
heart_data = pandas.read_csv("heart.csv")
#用Mapper定義特徵工程
mapper = DataFrameMapper([
(['sbp'], MinMaxScaler()),
(['tobacco'], MinMaxScaler()),
('ldl', None),
('adiposity', None),
(['famhist'], LabelBinarizer()),
('typea', None),
('obesity', None),
('alcohol', None),
(['age'], FunctionTransformer(np.log)),
])
#用pipeline定義使用的模型,特徵工程等
pipeline = PMMLPipeline([
('mapper', mapper),
("classifier", linear_model.LinearRegression())
])
pipeline.fit(heart_data[heart_data.columns.difference(["chd"])], heart_data["chd"])
#匯出模型檔案
sklearn2pmml(pipeline, "lrHeart.xml", with_repr = True)
heart.csv定義結構如下:
sbp,tobacco,ldl,adiposity,famhist,typea,obesity,alcohol,age,chd
160,12,5.73,23.11,Present,49,25.3,97.2,52,1
144,0.01,4.41,28.61,Absent,55,28.87,2.06,63,1
118,0.08,3.48,32.28,Present,52,29.14,3.81,46,0
170,7.5,6.41,38.03,Present,51,31.99,24.26,58,1
134,13.6,3.5,27.78,Present,60,25.99,57.34,49,1
132,6.2,6.47,36.21,Present,62,30.77,14.14,45,0
142,4.05,3.38,16.2,Absent,59,20.81,2.62,38,0
114,4.08,4.59,14.6,Present,62,23.11,6.72,58,1
114,0,3.83,19.4,Present,49,24.86,2.49,29,0
132,0,5.8,30.96,Present,69,30.11,0,53,1
206,6,2.95,32.27,Absent,72,26.81,56.06,60,1
134,14.1,4.44,22.39,Present,65,23.09,0,40,1
模型檔案匯出後,可以把檔案儲存在公司內部檔案儲存,實在不行可以打包在 jar 包中,供線上呼叫。
Java端採用 jpmml/jpmml-evaluator專案,載入 PMML 檔案,然後準備線上所需資料,示例程式碼如下:
//準備畫像資料-key和原始特徵一致即可
lrHeartInputMap.put("sbp", 142);
lrHeartInputMap.put("tobacco", 2);
lrHeartInputMap.put("ldl", 3);
lrHeartInputMap.put("adiposity", 30);
lrHeartInputMap.put("famhist", "Present");
lrHeartInputMap.put("typea", 83);
lrHeartInputMap.put("obesity", 23);
lrHeartInputMap.put("alcohol", 90);
lrHeartInputMap.put("age", 30);
//預測核心程式碼
public static void predictLrHeart() throws Exception {
PMML pmml;
//模型匯入
File file = new File("lrHeart.xml");
InputStream inputStream = new FileInputStream(file);
try (InputStream is = inputStream) {
pmml = org.jpmml.model.PMMLUtil.unmarshal(is);
ModelEvaluatorFactory modelEvaluatorFactory = ModelEvaluatorFactory.newInstance();
ModelEvaluator<?> modelEvaluator = modelEvaluatorFactory.newModelEvaluator(pmml);
Evaluator evaluator = (Evaluator) modelEvaluator;
List<InputField> inputFields = evaluator.getInputFields();
//過模型的原始特徵,從畫像中獲取資料,作為模型輸入
Map<FieldName, FieldValue> arguments = new LinkedHashMap<>();
for (InputField inputField : inputFields) {
FieldName inputFieldName = inputField.getName();
Object rawValue = lrHeartInputMap.get(inputFieldName.getValue());
FieldValue inputFieldValue = inputField.prepare(rawValue);
arguments.put(inputFieldName, inputFieldValue);
}
Map<FieldName, ?> results = evaluator.evaluate(arguments);
List<TargetField> targetFields = evaluator.getTargetFields();
//獲得結果,作為迴歸預測的例子,只有一個輸出。對於分類問題等有多個輸出。
for (TargetField targetField : targetFields) {
FieldName targetFieldName = targetField.getName();
Object targetFieldValue = results.get(targetFieldName);
System.out.println("target: " + targetFieldName.getValue() + " value: " + targetFieldValue);
}
}
}
特徵工程的說明
如流程圖所示,對於原始樣本資料,首先要做特徵工程輸入演算法需要資料。特徵工程可以線上線下,分開單獨做,也可以用 DataFrameMapper 的方式實現特徵工程,匯出到模型檔案中,這樣線上就不需要再實現一次特徵工程,但缺點是隻可以使用Sklearn包中提供的方法,自拓展的方法無法支援(例如 Bucketizer)
支援的特徵工程方法
preprocessing.Binarizer #二值化,例如大於 5 變成 1,小於等於 5 變成 0
preprocessing.FunctionTransformer # 支援 np 包中,簡單的一些函式,例如 np.log.
preprocessing.Imputer
preprocessing.LabelBinarizer # 對於string型別的,one-hot
preprocessing.LabelEncoder
preprocessing.MaxAbsScaler
preprocessing.MinMaxScaler # min max 歸一
preprocessing.OneHotEncoder # 對於數值的 one-hot
preprocessing.PolynomialFeatures #把兩個特徵,拆成多個N次組合特徵,例如x,y 變成 x y x^2 y^2 xy
preprocessing.RobustScaler
preprocessing.StandardScaler
Sklearn preprocessing 暫時不支援 分桶,該操作不久會被支援,已經有開源貢獻者提交了 Merge Request,在Sk支援後,相信不久 JPMML專案也會支援
https://github.com/scikit-learn/scikit-learn/pull/9342
XGBoost
模型原理的理解可以參考這篇文章:GBDT的原理和應用
XGBoost 輸入的檔案格式是 SVMLib 檔案格式。
1 3:1 10:1 11:1 21:1 30:1 34:1 36:1 40:1 41:1 53:1 58:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 105:1 117:1 124:1
0 3:1 10:1 20:1 21:1 23:1 34:1 36:1 39:1 41:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 120:1
0 1:1 10:1 19:1 21:1 24:1 34:1 36:1 39:1 42:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 122:1
1 3:1 9:1 19:1 21:1 30:1 34:1 36:1 40:1 42:1 53:1 58:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 105:1 117:1 124:1
第一列是 label,後面的是特徵Id對應的值。和他一起使用的,有個特徵檔案,定義了特徵工程方法。
0 cap-shape=bell i
1 cap-shape=conical i
2 cap-shape=convex i
3 cap-shape=flat i
4 cap-shape=knobbed i
i: indicator, 二值特徵
q: quantitative, 數值特徵,例如年齡等
int: int means this feature is integer value (when int is hinted, the decision boundary will be integer)
模型訓練完畢後,匯出模型檔案 (.model) 。使用 jpmml/jpmml-xgboost 轉模型檔案成PMML格式。 可以和feature map檔案作為輸入,這樣線上就不需要重新定義特徵工程。
示例命令如下: java -jar target/converter-executable-1.2-SNAPSHOT.jar --model-input 0002.model --fmap-input featmap.txt --target-name mpg --pmml-output xgboost.pmml
線上使用的方法和前例基本一致,準備好原始特徵值,過模型即可
xgBoostInputMap.put("mpg","1");
xgBoostInputMap.put("bruises?", "no");
xgBoostInputMap.put("odor", "creosote");
xgBoostInputMap.put("gill-spacing", "close");
xgBoostInputMap.put("gill-size", "narrow");
xgBoostInputMap.put("stalk-root", "club");
xgBoostInputMap.put("stalk-surface-below-ring", "smooth");
xgBoostInputMap.put("spore-print-color", "orange");
總結
因為PMML格式的通用性,所以會喪失特殊模型的特殊優化,例如上線XGBoost模型,也可以使用XGBoost4J,該包會連結一個本地環境編譯的 .so 檔案,C++實現的核心程式碼效率很高。不過PMML格式通用,在效率要求不高的場景可以發揮很大作用。
對於特徵工程部分的計算,PMML對特徵工程對支援有限,線上、線下可以單獨實現,PMML檔案只負責模型部分,這樣既可以做豐富的特徵工程,也實現了模型的共用。
傳送門:https://zhuanlan.zhihu.com/p/30378213
---------------------
作者:文西
來源:CSDN
原文:https://blog.csdn.net/hopeztm/article/details/78321700
版權宣告:本文為博主原創文章,轉載請附上博文連結!