大家都知道,Python 和 SAS 是兩個很常用的資料探勘工具。Python 開源、免費、有豐富的三方庫,一般在網際網路公司廣泛使用。而SAS需付費,且費用較高,一般網際網路公司無法承擔,更多的是在銀行等傳統金融機構中使用,不過這兩年由於Python太火,原本使用SAS的也開始逐漸轉向Python了。
擁抱開源,越來越多的愛好者造出優秀的Python輪子,比如當下比較流行的萬金油模型Xgboost、LightGBM,在各種競賽的top級方案中均有被使用。而SAS的腳步就比較慢了,對於一些比較新的東西都無法直接提供,所以對於那些使用SAS的朋友,就很難受了。
一直以來很多粉絲問過我這個問題:有沒有一種可以將Python模型轉成SAS的工具?
因為我本身是兩個技能都具備的,實際工作中一般都是配合使用,也很少想過進行轉換。但是,最近逛技術論壇剛好發現了一個騷操作,藉助Python的三方庫m2cgen和Python指令碼即可完成Python模型到SAS的轉換。
m2cgen是什麼?m2cgen是一個Python的第三方庫,主要功能就是將Python訓練過的模型轉換為其它語言,比如 R 和 VBA。遺憾的是,目前m2cgen尚不支援SAS,但這並不影響我們最終轉換為SAS。
我們仍然使用m2cgen,需要藉助它間接轉換成SAS。具體的方案就是先將Python模型轉換為VBA程式碼,然後再將VBA程式碼更改為 SAS指令碼,曲線救國。
如何使用m2cgen?我直接用一個例子說明下如何操作。
資料我們使用sklearn自帶的iris dataset,連結如下:
https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html
下面,演示一下如何將Python的XGBoost模型轉成SAS程式碼。
首先匯入所需的庫包和資料。
# 匯入庫import pandas as pdimport numpy as npimport os import refrom sklearn import datasetsfrom xgboost import XGBClassifierfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import accuracy_scoreimport m2cgen as m2c# 匯入資料iris = datasets.load_iris()X = iris.dataY = iris.target
然後,我們劃分資料集,直接扔進XGBoost裡面,建立base模型。
# 劃分資料為訓練集和測試集seed = 2020test_size = 0.3X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed)# 訓練資料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指令碼即可實現VBA指令碼轉換為SAS指令碼。
改動的地方不多,主要包括:刪除在SAS環境中不能使用的程式碼,像上面結果中的Module xxx,Function yyy ,Dim var Z As Double,還有在語句結尾加上;,這些為的就是遵循SAS的語法規則。
下面就是轉換的Python指令碼,可以自動執行上面所說的轉換操作。
# 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來替代inputVectordictionary = {'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、開頭、結尾、輸出名稱
使用過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中的語法規則,還需將每個語句的結尾加上;。仍用正則表示式,然後for迴圈在每一行最後新增字元;即可。
# 在結尾加上分號';'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與變數名稱對映到輸入資料集中,一次性更改所有InputVector。
# 用var來替代inputVectordictionary = {'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