太騷了!Python模型完美切換SAS,還能這么玩。。

作者:東哥起飛
來源:Python數(shù)據科學
大家都知道,Python 和 SAS 是兩個很常用的數(shù)據挖掘工具。Python 開源、免費、有豐富的三方庫,一般在互聯(lián)網公司廣泛使用。而SAS需付費,且費用較高,一般互聯(lián)網公司無法承擔,更多的是在銀行等傳統(tǒng)金融機構中使用,不過這兩年由于Python太火,原本使用SAS的也開始逐漸轉向Python了。
擁抱開源,越來越多的愛好者造出優(yōu)秀的Python輪子,比如當下比較流行的萬金油模型Xgboost、LightGBM,在各種競賽的top級方案中均有被使用。而SAS的腳步就比較慢了,對于一些比較新的東西都無法直接提供,所以對于那些使用SAS的朋友,就很難受了。
一直以來很多粉絲問過東哥這個問題:有沒有一種可以將Python模型轉成SAS的工具?
因為我本身是兩個技能都具備的,實際工作中一般都是配合使用,也很少想過進行轉換。但是,最近東哥逛技術論壇剛好發(fā)現(xiàn)了一個騷操作,借助Python的三方庫m2cgen和Python腳本即可完成Python模型到SAS的轉換。
m2cgen是什么?
m2cgen是一個Python的第三方庫,主要功能就是將Python訓練過的模型轉換為其它語言,比如 R 和 VBA。遺憾的是,目前m2cgen尚不支持SAS,但這并不影響我們最終轉換為SAS。
我們仍然使用m2cgen,需要借助它間接轉換成SAS。具體的方案就是先將Python模型轉換為VBA代碼,然后再將VBA代碼更改為 SAS腳本,曲線救國。
如何使用m2cgen?
我直接用一個例子說明下如何操作。
數(shù)據我們使用sklearn自帶的iris dataset,鏈接如下:
https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html
下面,演示一下如何將Python的XGBoost模型轉成SAS代碼。
首先導入所需的庫包和數(shù)據。
# 導入庫
import pandas as pd
import numpy as np
import os
import re
from sklearn import datasets
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import m2cgen as m2c
# 導入數(shù)據
iris = datasets.load_iris()
X = iris.data
Y = iris.target
然后,我們劃分數(shù)據集,直接扔進XGBoost里面,建立base模型。
# 劃分數(shù)據為訓練集和測試集
seed = 2020
test_size = 0.3
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed)
# 訓練數(shù)據
model = XGBClassifier()
model.fit(X_train, y_train)
然后,再將XGBoost模型轉換為VBA。使用m2cgen的export_to_visual_basic方法就可以直接轉成VBA了。轉換成其他語言腳本也是同理,非常簡單。
code = m2c.export_to_visual_basic(model, function_name = 'pred')
核心的騷操作來了!
m2cgen不支持SAS,但我們可以把VBA代碼稍加改動,就能變成符合SAS標準的代碼了。而這個改動也無需手動一個個改,寫一段Python腳本即可實現(xiàn)VBA腳本轉換為SAS腳本。
改動的地方不多,主要包括:刪除在SAS環(huán)境中不能使用的代碼,像上面結果中的Module xxx,Function yyy ,Dim var Z As Double,還有在語句結尾加上;,這些為的就是遵循SAS的語法規(guī)則。
下面就是轉換的Python腳本,可以自動執(zhí)行上面所說的轉換操作。
# 1、移除SAS中不能使用的代碼
code = re.sub('Dim var.* As Double', '', code)
code = re.sub('End If', '', code)
# 下面操作將修改成符合SAS的代碼
# 2、修改起始
code = re.sub('Module Model\nFunction pred\(ByRef inputVector\(\) As Double\) As Double\(\)\n',
'DATA pred_result;\nSET dataset_name;', code)
# 3、修改結尾
code = re.sub('End Function\nEnd Module\n', 'RUN;', code)
# 4、在結尾加上分號';'
all_match_list = re.findall('[0-9]+\n', code)
for idx in range(len(all_match_list)):
original_str = all_match_list[idx]
new_str = all_match_list[idx][:-1]+';\n'
code = code.replace(original_str, new_str)
all_match_list = re.findall('\)\n', code)
for idx in range(len(all_match_list)):
original_str = all_match_list[idx]
new_str = all_match_list[idx][:-1]+';\n'
code = code.replace(original_str, new_str)
# 用var來替代inputVector
dictionary = {'inputVector(0)':'sepal_length',
'inputVector(1)':'sepal_width',
'inputVector(2)':'petal_length',
'inputVector(3)':'petal_width'}
for key in dictionary.keys():
code = code.replace(key, dictionary[key])
# 修改預測標簽
code = re.sub('Math.Exp', 'Exp', code)
code = re.sub('pred = .*\n', '', code)
temp_var_list = re.findall(r"var[0-9]+\(\d\)", code)
for var_idx in range(len(temp_var_list)):
code = re.sub(re.sub('\\(', '\\(', re.sub('\\)', '\\)', temp_var_list[var_idx])), iris.target_names[var_idx]+'_prob', code)
對以上腳本分步解釋說明一下。
1、開頭、結尾、輸出名稱
前三個部分非常簡單。使用正則表達式刪除多余的行,然后將腳本的開頭更改為DATA pred_result; \ nSETdataset_name;。
使用過SAS的同學就很熟悉了,pred_result是運行SAS腳本后的輸出表名稱,dataset_name是我們需要預測的輸入表名稱。
最后再將腳本的結尾更改為RUN;。
# 移除SAS中不能使用的代碼
code = re.sub('Dim var.* As Double', '', code)
code = re.sub('End If', '', code)
# 下面操作將修改成符合SAS的代碼
# 修改起始
code = re.sub('Module Model\nFunction pred\(ByRef inputVector\(\) As Double\) As Double\(\)\n',
'DATA pred_result;\nSET dataset_name;', code)
# 修改結尾
code = re.sub('End Function\nEnd Module\n', 'RUN;', code)
2、語句末尾添加分號
為遵循SAS中的語法規(guī)則,還需將每個語句的結尾加上;。仍用正則表達式,然后for循環(huán)在每一行最后添加字符;即可。
# 在結尾加上分號';'
all_match_list = re.findall('[0-9]+\n', code)
for idx in range(len(all_match_list)):
original_str = all_match_list[idx]
new_str = all_match_list[idx][:-1]+';\n'
code = code.replace(original_str, new_str)
all_match_list = re.findall('\)\n', code)
for idx in range(len(all_match_list)):
original_str = all_match_list[idx]
new_str = all_match_list[idx][:-1]+';\n'
code = code.replace(original_str, new_str)
3、映射變量名稱
使用字典將InputVector與變量名稱映射到輸入數(shù)據集中,一次性更改所有InputVector。
# 用var來替代inputVector
dictionary = {'inputVector(0)':'sepal_length',
'inputVector(1)':'sepal_width',
'inputVector(2)':'petal_length',
'inputVector(3)':'petal_width'}
for key in dictionary.keys():
code = code.replace(key, dictionary[key])
4、映射變量名稱
最后一步就是更改預測標簽。
# 修改預測標簽
code = re.sub('Math.Exp', 'Exp', code)
code = re.sub('pred = .*\n', '', code)
temp_var_list = re.findall(r"var[0-9]+\(\d\)", code)
for var_idx in range(len(temp_var_list)):
code = re.sub(re.sub('\\(', '\\(', re.sub('\\)', '\\)', temp_var_list[var_idx])), iris.target_names[var_idx]+'_prob', code)
然后保存sas模型文件。
#保存輸出
vb = open('vb1.sas', 'w')
vb.write(code)
vb.close()
最后,為了驗證sas腳本是否正確,我們將sas模型的預測結果和Python的結果進行一下對比。
# python 預測
python_pred = pd.DataFrame(model.predict_proba(X_test))
python_pred.columns = ['setosa_prob','versicolor_prob','virginica_prob']
python_pred
# sas 預測
sas_pred = pd.read_csv('pred_result.csv')
sas_pred = sas_pred.iloc[:,-3:]
sas_pred
(abs(python_pred - sas_pred) > 0.00001).sum()
可以看到,兩個預測的結果基本上一樣,基本沒問題,我們就可以在sas中跑xgboost模型了。
總結
上面只是個最簡單的示例,沒有對特征處理。對于復雜的建模過程,比如很多特征工程,那就要對Python腳本進一步調整了。
覺得這個方法比較新穎,拿來分享一下,大家也可舉一反三。如果鐵子們有更多好玩的方法,歡迎文章下面留言
。
參考:https://towardsdatascience.com/converting-machine-learning-models-to-sas-using-m2cgen-python-190d846090dc
