看了一些別人的思路,總結了一些模型性能提升的操作並完成python實現。
1. 行空缺值的處理
常規方法
統計每行數據的空缺值,如果空缺值數量超過閾值,則剔除此行數據。
改進方法
考慮特征重要度的因素。遵循一個原則:特征重要度越高,對這一特征下的空缺值容忍程度越低。
特征重要度的評估手段
1.1 輸入特征方差膨脹系數或者方差
對特征歸一化處理后,計算各特征方差膨脹系數或者方差。方差越大,說明這一特征數據波動性越大,對模型的貢獻程度也就越高。試想一下,若一個特征的數據值全為1,則說明這一個特征對模型並沒有產生什么貢獻。
import pandas as pd import numpy as np def var_filter(data, label, k=0): """ 計算dataframe中輸入特征方差並按閾值返回dataframe :param data: dataframe數據集,包括輸入輸出 :param label: 輸出特征 :param k: 方差閾值 :return: 按閾值返回dataframe """ features = data.drop([label], axis=1).columns saved_features = [] for feature in features: feature_var = np.array(data[feature]).var() print('輸入特征{0}的方差為:{1}'.format(feature, feature_var)) if feature_var > k: saved_features.append(feature) saved_features.append(label) filter_data = data[saved_features] return filter_data
1.2 輸入特征共線性檢驗
所謂輸入特征共線性即各特征之間存在線性相關的程度,共線性問題有如下幾種檢驗方法:
相關性分析,檢驗變量之間的相關系數;
方差膨脹因子VIF,當VIF大於5或10時,代表模型存在嚴重的共線性問題;
條件數檢驗,當條件數大於100、1000時,代表模型存在嚴重的共線性問題。
這里,我們采用特征間皮爾遜相關系數作為共線性判斷依據。
import pandas as pd import numpy as np
def vif_test(data, label, k=None): """ 計算dataframe中輸入特征之間的共線性系數 :param data: dataframe數據集,包括輸入輸出 :param label: 輸出特征 :param k: 相關系數閾值 :return: 按閾值返回dataframe """ features = data.drop([label], axis=1).columns feature_array = np.array(data[features]) print(feature_array) vif_array = np.corrcoef(feature_array, rowvar=0) print(vif_array) for idx in range(len(features) - 1): for count in range(idx + 1, len(features)): vif = vif_array[idx][count] print('特征{0}與特征{1}的共線性系數vif為:{2}'.format(features[idx], features[count], vif))
1.3 輸入特征與輸出特征之間的皮爾遜相關系數
計算各輸入特征與輸出特征之間的皮爾遜相關系數,相關系數越高,說明特征的貢獻程度越高,也就是說這一特征的重要度越大。
import pandas as pd
import numpy as np
def pearson_value(data, label, k=None): """ 計算dataframe中輸入特征與輸出特征之間的pearson相關系數並按閾值返回dataframe :param data: dataframe數據集,包括輸入輸出 :param label: 輸出特征 :param k: 相關系數閾值 :return: 按閾值返回dataframe """ features = data.drop([label], axis=1).columns label_array = np.array(data[label]) saved_features = [] for feature in features: feature_array = np.array(data[feature]) vstack = np.vstack([feature_array, label_array]) pearson_coef = np.corrcoef(vstack)[0][1] print('輸入特征{0}與輸出特征{1}的皮爾遜相關系數為:{2}'.format(feature, label, pearson_coef)) if pearson_coef > k: saved_features.append(feature) saved_features.append(label) filter_data = data[saved_features] return filter_data
1.4 互信息檢驗
計算各特征與輸出特征之間的互信息( sklearn.metrics .mutual_info_score())
from sklearn import metrics as mr def mutual_info(data, label, k=None): """ 計算dataframe中輸入特征與輸出特征之間的互信息mi並按閾值返回datframe :param data: dataframe數據集,包括輸入輸出 :param label: 輸出特征 :param k: 相關系數閾值 :return: 按閾值返回dataframe """ features = data.drop([label], axis=1).columns label_array = np.array(data[label]) saved_features = [] for feature in features: feature_array = np.array(data[feature]) mi = mr.mutual_info_score(label_array, feature_array) print('輸入特征{0}與輸出特征{1}的互信息為:{2}'.format(feature, label, mi)) if mi > k: saved_features.append(feature) saved_features.append(label) filter_data = data[saved_features] return filter_data
1.5 在計算特征與輸出特征之間的相關系數或者互信息的同時,考慮特征間的關系。
常用的方法如下:
- Xgb’s importance
- Logistic regression的params的參數
- Recursive feature elimination(遞歸參數選擇方法 RFE)
- Pearson Correlation
- Distance correlation
遞歸參數選擇方法是通過考慮越來越小的特征集合來遞歸的選擇特征。 首先,評估器在初始的特征集合上面訓練並且每一個特征的重要程度是通過一個 coef_ 屬性 或者 feature_importances_ 屬性來獲得。 然后,從當前的特征集合中移除最不重要的特征。在特征集合上不斷的重復遞歸這個步驟,直到最終達到所需要的特征數量為止。
這里以xgb’s的 feature_importance_ 和 Recursive feature elimination(遞歸參數選擇)為例,其中xgboost的python安裝環境可以從 https://www.lfd.uci.edu/~gohlke/pythonlibs/#xgboost 選擇對應的python版本下載。
xgboost提供了兩種訓練模型的API接口,分別是基於XGBoost的原生接口以及基於Scikit-learn接口。下面會各自用着兩類接口分別做回歸、分類模型的特征重要度展示及輸出。
1.5.1 利用scikit-learn接口完成xgboost多分類模型訓練及特征重要度展示:
import xgboost as xgb from xgboost import plot_importance from matplotlib import pyplot as plt from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score def xg_feature_importance(data, label): """ 利用scikit-learn接口完成xgboost多分類模型訓練及特征重要度展示 :param data: dataframe數據集,包括輸入輸出 :param label: 輸出特征 :return: """ X = np.array(data.drop([label], axis=1)) y = np.array(data[label]) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) # 訓練分類模型 model = xgb.XGBClassifier(max_depth=5, learning_rate=0.1, n_estimators=160, silent=True, objective='multi:softmax') model.fit(X_train, y_train) # 對測試集進行預測 y_predict = model.predict(X_test) # 計算准確率 accuracy = accuracy_score(y_test, y_predict) print("Accuracy: %.2f %% " % (100 * accuracy)) # 顯示重要特征 plot_importance(model) plt.show() # 打印特征重要度 print(model.feature_importances_)
1.5.2 利用scikit-learn接口完成xgboost回歸模型訓練及特征重要度展示:
import xgboost as xgb from xgboost import plot_importance from matplotlib import pyplot as plt from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error def xg_feature_importance(data, label): """ 利用scikit-learn接口完成xgboost回歸模型訓練及特征重要度展示 :param data: dataframe數據集,包括輸入輸出 :param label: 輸出特征 :return: """ X = np.array(data.drop([label], axis=1)) y = np.array(data[label]) # XGBoost訓練過程 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) params = { 'booster': 'gbtree', 'objective': 'reg:gamma', 'gamma': 0.1, 'max_depth': 5, 'lambda': 3, 'subsample': 0.7, 'colsample_bytree': 0.7, 'min_child_weight': 3, 'silent': 1, 'eta': 0.1, 'seed': 1000, 'nthread': 4, } dtrain = xgb.DMatrix(X_train, y_train) num_rounds = 126 model = xgb.train(params, dtrain, num_rounds) # 對測試集進行預測 dtest = xgb.DMatrix(X_test) y_predict = model.predict(dtest) # 計算MSE mse = mean_squared_error(y_test, y_predict) print("MSE: %.2f " % (mse)) # 顯示重要特征 plot_importance(model) plt.show() # 打印特征重要度 print(model.get_fscore().items())
1.5.3 利用scikit-learn 包里面的RFECV遞歸參數選擇方法完成特征選擇:
import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import StratifiedKFold from sklearn.feature_selection import RFECV from sklearn.datasets import make_classification def rfecv(data, label): """ 遞歸參數選擇方法 RFE計算特征重要度 :param data: :param label: :return: """ # Build a classification task using 3 informative features # X, y = make_classification(n_samples=1000, n_features=25, n_informative=3, # n_redundant=2, n_repeated=0, n_classes=8, # n_clusters_per_class=1, random_state=0) X = np.array(data.drop([label], axis=1)) y = np.array(data[label]) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=0) # Create the RFE object and compute a cross-validated score. svc = SVC(kernel="linear") # classifications rfecv = RFECV(estimator=svc, step=1, cv=StratifiedKFold(2), scoring='accuracy') rfecv.fit(X_train, y_train) # 最佳特征數量 print('最佳特征數量:{}'.format(rfecv.n_features_)) # 每個特征的等級 ,估計最好的特征被分配到等級1。 print('每個特征的排序等級:{}'.format(rfecv.ranking_)) for idx, rank in enumerate(rfecv.ranking_): if rfecv.ranking_[idx] == 1: print('排序等級為1的特征是第{}個特征'.format(idx)) # Plot number of features VS. cross-validation scores plt.figure() plt.xlabel("選擇的特征數") plt.ylabel("交叉驗證得分") plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_) plt.show()
2. 列空缺值的處理
常規方法
觀察特征數據分布,如果是連續變量且正態分布,用平均數或眾數填充,如果偏態分布,用分位數填充。解釋性相對不太強
改進方法
特征分箱。
特征分箱主要有以下優點 :
- 可以將缺失作為獨立的一類帶入模型;
- 稀疏向量內積乘法運算速度快,計算結果方便存儲,容易擴展;
- 保存了原始的信息,沒有以填充或者刪除的方式改變真實的數據分布;
- 讓特征存在的形式更加合理,比如age這個字段,其實我們在乎的不是27或者28這樣的差別,而是90后,80后這樣的差別,如果不采取分箱的形式,一定程度上誇大了27與26之前的差異;
- 在數據計算中,不僅僅加快了計算的速度而且消除了實際數據記錄中的隨機偏差,平滑了存儲過程中可能出現的噪音;
分箱操作示例(參考:數據分箱技術Binning):
import numpy as np import pandas as pd # 對series分箱 score_list = np.random.randint(low=25, high=100, size=20) # 分箱原則,規定:0-59為不及格,59-70為一般,70-80為良好,80-100位優秀: bins = [0, 59, 70, 80, 100] score_cut = pd.cut(score_list, bins) # 統計各個段內數據的個數 pd.value_counts(score_cut) # 對dataframe分箱 df = pd.DataFrame() df['score'] = score_list # pd.util.testing.rands(3) for i in range(20)可以生成20個隨機3位字符串。 df['student'] = [pd.util.testing.rands(3) for i in range(20)] # 使用前面的bins標准對df進行分箱,得到一個categories 對象 df['categories'] = pd.cut(df['score'], bins, labels=['low', 'ok', 'good', 'great'])

3. 特征工程之特征交叉
在構造的具有可解釋性特征的基礎上,構造交叉特征,例如可以使用FM構造兩兩交叉特征(關於FM算法的部分,可以參考我的另一篇文章:FM算法解析及Python實現 )。
需要注意的是,原始特征量較大的情況下,直接使用FM算法的方式進行特征構造,會使特征成倍增加。例如N個特征兩兩相乘,會產生N(N-1)/2個新特征。
所以,可以利用Xgboost或者隨機森林對原始特征繪制特征重要度折線圖,並重點關注拐點的特征,在拐點之間選擇篩選出需要做交叉特征的原始特征及數量。從而減少交叉特征的大量出現。
4. 模型融合
模型融合不僅泛化性有提高,同時還會一定程度上提高預測的准確率,並且當模型融合中的基學習器之間互相獨立時,模型融合的方法效果會更好。
常規方法
4.1 bagging
bagging 融合算法的目標是在每個子模塊的設計選擇過程中要盡可能的保證:
- low biase
- high var
也就是說子模塊可以適當的過擬合,增加子模型擬合准確程度,通過加權平均的時候可以降低泛化誤差
from sklearn.ensemble import BaggingClassifier from sklearn.neighbors.classification import KNeighborsClassifier def bag_class(x_train, y_train, max_samples, max_features, bootstrap, bootstrap_features, oob_score): """ bagging method :param x_train: input trainset :param y_train: output trainset :param max_samples: The number of samples to draw from X to train each base estimator. :param max_features: The number of features to draw from X to train each base estimator. :param bootstrap: boolean, optional (default=True) Whether samples are drawn with replacement. :param bootstrap_features: boolean, optional (default=False) Whether features are drawn with replacement. :param oob_score: bool.Whether to use out-of-bag samples to estimate the generalization error. :return: bagging model """ bagging = BaggingClassifier(KNeighborsClassifier(), max_samples=max_samples, max_features=max_features, bootstrap=bootstrap, bootstrap_features=bootstrap_features, oob_score=oob_score) model = bagging.fit(x_train, y_train) return model
4.2 voting (bagging的改進型)
不同類型模型(樹模型、線性模型、SVM等)通過硬投票或者軟投票得到最終預測結果
from sklearn.ensemble import VotingClassifier from sklearn.ensemble import GradientBoostingClassifier from sklearn.naive_bayes import GaussianNB from sklearn.ensemble import RandomForestClassifier def voting_class(x_train, y_train, vote_type='hard', weights=None): """ Voting Classifier voting by majority models or voting by setting models weight of vote :param vote_type: 'hard' ,'soft' :param weights: list [1,2,1] or [2,1,2] etc when set vote_type is 'soft' :return: classifier model """ clf1 = GradientBoostingClassifier(random_state=1) clf2 = RandomForestClassifier(random_state=1) clf3 = GaussianNB() eclf = VotingClassifier(estimators=[('gbc', clf1), ('rf', clf2), ('gnb', clf3)], voting=vote_type, weights=weights) model = eclf.fit(x_train, y_train) return model
4.3 boosting
改進方法
4.4 stacking方法
stacking融合算法的目標是在每個子模塊1、子模塊2的設計選擇過程中要盡可能的保證:
- high biase
- low var
在子模塊3的時候,要保證:
- low biase
- high var
也就是說,在子模塊1,2的選擇中,我們需要保證可稍欠擬合,在子模塊3的擬合上再保證擬合的准確度及強度(增加樹的深度max_depth、內部節點再划分所需最小樣本數min_samples_split、葉子節點樣本數min_samples_leaf、最大葉子節點數max_leaf_nodes等,可參考文章:scikit-learn 梯度提升樹(GBDT)調參小結)
from sklearn import model_selection from sklearn.neighbors import KNeighborsClassifier from sklearn.ensemble import GradientBoostingClassifier from sklearn.naive_bayes import GaussianNB from sklearn.ensemble import RandomForestClassifier from mlxtend.classifier import StackingClassifier def stacking_class(x_train, y_train, use_probas=False, average_probas=False): """ Stacking model ensemble basic model high biase and low var (underfitting),final model low biase and high var (overfitting) :param x_train: input trainset :param y_train: output trainset :param use_probas: True: the class probability generated from layer 1 basic classifier as the final total meta-classfier's input False: the feature output generated by the basic classifier is used as the input of the final total meta-classifier :param average_probas: True: the probability value generated by the basic classifier for each category will be averaged, otherwise(False) it will be spliced. :return: stacking model """ clf1 = KNeighborsClassifier(n_neighbors=1) clf2 = RandomForestClassifier(random_state=1) clf3 = GaussianNB() # final model設計的overfitting final_clf = GradientBoostingClassifier(max_depth=10,max_leaf_nodes=10) sclf = StackingClassifier(classifiers=[clf1, clf2, clf3], use_probas=use_probas, average_probas=average_probas, meta_classifier=final_clf) for clf, label in zip([clf1, clf2, clf3, sclf], ['KNN', 'Random Forest', 'Naive Bayes', 'StackingClassifier']): scores = model_selection.cross_val_score(clf, x_train, y_train, cv=5, scoring='accuracy') print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label)) model = sclf.fit(x_train, y_train) return model
4.2 blending
我們知道單個組合子模塊的結果不夠理想,如果想得到更好的結果,需要把很多單個子模塊的結果融合在一起:
這種方法也可以提高我們最后的預測的效果。


