摘要:np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))。scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error))。

Datawhale 

作者:徐韜 ,Datawhale優秀學習者

摘要:對於數據挖掘項目, 本文將學習如何建模調參?從簡單的模型開始,如何去建立一個模型;如何進行交叉驗證;如何調節參數優化等。

建模調參 特徵工程也好,數據清洗也罷,都是爲最終的模型來服務的,模型的建立和調參決定了最終的結果。模型的選擇決定結果的上限, 如何更好的去達到模型上限取決於模型的調參。

數據及背景

https://tianchi.aliyun.com/competition/entrance/231784/information (阿里天池-零基礎入門數據挖掘)

理論簡介

模型調參 基於特徵工程 所構建的模型上限來優化模型。由於模型的不同和複雜度,模型的參數數量也都不一樣。線性模型需要調整正則化的係數,而對於非線性模型,例如隨機森林和LGB等 模型 ,需要調節的參數增多。

模型調參的目的就是提升模型的性能度量。對於迴歸算法,我們要降低模型在未知的數據上的誤差;對於分類算法,我們要提高模型在未知數據上的準確率

知識總結

迴歸分析

迴歸分析 是一種統計學上分析數據的方法,目的在於瞭解兩個或多個變量間是否相關、相關方向與強度,並建立數學模型。以便通過觀察特定變量(自變量),來預測研究者感興趣的變量(因變量)

般形式

向量 形式

其中 向量代表一條樣本 ,其中 代表樣本的各個特徵, 是一條向量代表了每個特徵所佔的權重,b是一個標量代表特徵都爲0時的預測值,可以視爲模型的basis或者bias。

失函數 我們希望的是能夠減少在測試集上的預測值 與真實值 的差別,從而獲得一個最佳的權重參數,因此這裏採用最小二乘估計。

長尾分佈

這種分佈會使得采樣不準,估值不準,因爲尾部佔了很大部分。另一方面,尾部的數據少,人們對它的瞭解就少,那麼如果它是有害的,那麼它的破壞力就非常大,因爲人們對它的預防措施和經驗比較少。

欠擬合與過擬合

欠擬 訓練的模型在訓練集上面的表現很差,在驗證集上面的表現也很差。即訓練誤差和泛化誤差都很大。 因:

  • 模型沒有很好或足夠數量的訓練訓練集

  • 模型的訓練特徵過於簡單

過擬 模型的訓練誤差遠小於它在測試數據集上的誤差。即訓練誤差不錯,但是泛化誤差比訓練誤差相差太多。 原因:

  • 模型沒有很好或足夠數量的訓練訓練集

  • 訓練 數據和測試數據有偏差

  • 模型的訓練過度,過於複雜,沒有學到主要的特徵

由此引 出模型複雜度概念模型中的參數,一個簡單的二元線性的函數只有兩個權重,而多元的複雜的函數的權重可能會什麼上百上千個。

模型複雜 度太低(參數過少),模型學習得太少,就難以訓練出有效的模型,便會出現欠擬合。模型複雜度太高(參數很多),即模型可訓練空間很大,容易學習過度,甚至於也將噪聲數據學習了,便會出現過擬合。

正則化

失函數後面會添加一個額外項,稱作 L1正則化 和 L2正則化,或者 L1範數和 L2範數。

L1正則化和L2正則化可以看做是損失函數的懲罰項。所謂『懲罰』是指對損失函數中的某些參數做一些限制。對於線性迴歸模型,使用L1正則化的模型建叫做Lasso迴歸,使用L2正則化的模型叫做Ridge迴歸(嶺迴歸)。

L 1正則化模型:

L 2正則化模型:

正則化說明:

  • L1正則化是指權值向量 中各個元素的絕對值之和,通常表示爲

  • L 2正則化是指權值向量 中各個元素的平方和然後再求平方根(可以看到Ridge迴歸的L2正則化項有平方符號)

正則化作用:

  • L1正則化可以產生稀疏權值矩陣,即產生一個稀疏模型,可以用於特徵選擇

  • L2正則化可以防止模型過擬合(overfitting)

調參方法

貪心調參 (座標下降)

座標下降法是一類優化算法,其最大的優勢在於不用計算待優化的目標函數的梯度。最容易想到一種特別樸實的類似於座標下降法的方法,與座標下降法不同的是,不是循環使用各個參數進行調整,而是貪心地選取了對整體模型性能影響最大的參數。參數對整體模型性能的影響力是動態變化的,故每一輪座標選取的過程中,這種方法在對每個座標的下降方向進行一次直線搜索(line search)

網格調參GridSearchCV

作用是在指定的範圍內可以自動調參,只需將參數輸入即可得到最優化的結果和參數。相對於人工調參更省時省力,相對於for循環方法更簡潔靈活,不易出錯。

貝葉斯調參

貝葉斯優化通過基於目標函數的過去評估結果建立替代函數(概率模型),來找到最小化目標函數的值。貝葉斯方法與隨機或網格搜索的不同之處在於,它在嘗試下一組超參數時,會參考之前的評估結果,因此可以省去很多無用功。

參數的評估代價很大,因爲它要求使用待評估的超參數訓練一遍模型,而許多深度學習模型動則幾個小時幾天才能完成訓練,並評估模型,因此耗費巨大。貝葉斯調參發使用不斷更新的概率模型,通過推斷過去的結果來“集中”有希望的超參數。

建模與調參

線性迴歸

模型建立

先使 用線性迴歸來查看一下用線性迴歸模型來擬合我們的題目會有那些缺點。 裏使用了 sklearn 的 LinearRegression。

sklearn.linear_model.LinearRegression(fit_intercept=True,normalize=False,copy_X=True,n_jobs=1

model = LinearRegression(normalize=True)

model.fit(data_x, data_y)


model.intercept_, model.coef_

看訓練的線性迴歸模型的截距與權重

'intercept:'+ str(model.intercept_)

sorted(dict(zip(continuous_feature_names, model.coef_)).items(), key=lambda x:x[1], reverse=True)

## output

對上下文代碼涉及到的函數功能進行簡單介紹:
zi p() 可以將兩個可迭代的對象,組合返回成一個元組數據
dict() 元組數據構建字典
it ems() 以列表返回可遍歷的(鍵, 值) 元組數組
sort(iterable, cmp, key, reverse) 排序函數
iterable 指定要排序的 list或者iterable
key 指定取待排序元素的哪一項進行排序 - 這裏x[1]表示按照列表中第二個元素排序
reverse 是一個bool變量,表示升序還是降序排列,默認爲False(升序)
np. quantile(train_y, 0.9) 求train_y 的90%的分位數

下面 這個代碼是把價格大於90%分位數的部分截斷了,就是長尾分佈截斷

繪製 特徵v_9的值與標籤的散點圖,圖片發現模型的預測結果(藍色點)與真實標籤(黑色點)的分佈差異較大。

且預測值p rice出現負數, 查看price分佈 出現長尾分佈 不符合正態分佈。

線性迴歸解決方案

1. 進行log變化

2. 進行可視化, 發現預測結果與真實值較爲接近,且未出現異常狀況。

交叉 驗證

大概說 一下sklearn的交叉驗證的使用方法, 下文會有很多使用:

v erbose 日誌顯示
verbose = 0 爲不在標準輸出流輸出日誌信息
verbose = 1 爲輸出進度條記錄
verbose = 2 爲每個epoch輸出一行記錄

K折交叉驗證是將原始數據分成K組,將每個子集數據分別做一次驗證集,其餘的K-1組子集數據作爲訓練集,這樣會得到K個模型,用這K個模型最終的驗證集分類準確率的平均數,作爲此K 折交叉驗證下分類器的性能指標。此處,採用五折交叉驗證。

data_y = np.log(data_y + 1)

# 交叉驗證

scores = cross_val_score(LinearRegression(normalize=True), X=data_x, \

y=data_y, cv=5, scoring=make_scorer(mean_absolute_error))


np.mean(scores)

但在事 實上,由於我們並不具有預知未來的能力,五折交叉驗證在某些與時間相關的數據集上反而反映了不真實的情況。

通過2018年的二手車價格預測2017年的二手車價格,這顯然是不合理的,因此我們還可以採用時間順序對數據集進行分隔。

在本例中,我們選用靠前時間的4/5樣本當作訓練集,靠後時間的1/5當作驗證集,最終結果與五折交叉驗證差距不大。

import datetime

sample_feature = sample_feature.reset_index(drop=True)

split_point = len(sample_feature) // 5 * 4

train = sample_feature.loc[:split_point].dropna()

val = sample_feature.loc[split_point:].dropna()


train_X = train[continuous_feature_names]

train_y_ln = np.log(train['price'] + 1)

val_X = val[continuous_feature_names]

val_y_ln = np.log(val['price'] + 1)


model = model.fit(train_X, train_y_ln)

  • fill_between()

  • train_sizes - 第一個參數表示覆蓋的區域

  • train_scores_mean - train_scores_std - 第二個參數表示覆蓋的下

  • train_scores_mean + train_scores_std - 第三個參數表示覆蓋的上限

  • color - 表示覆蓋區域的顏色

  • alpha - 覆蓋區域的透明度,越大越不透明 [0,1]

測結果查看:

mean_absolute_error(val_y_ln, model.predict(val_X))
0.19443858353490887

可視化處理

繪製學習率曲線與驗證曲線

線性模型

來對比一下三個lr模型的情況:

models = [LinearRegression(),

Ridge(),

Lasso()]

result = dict()

for model in models:

model_name = str(model).split('(')[0]

scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error))

result[model_name] = scores

print(model_name + ' is finished')


result = pd.DataFrame(result)

result.index = ['cv' + str(x) for x in range(1, 6)]

result

結果對比如下:

Line arRegression 線性迴歸:

Lass o迴歸:L1正則化有助於生成一個稀疏權值矩陣,進而可以用於特徵選擇。由此發現power與userd_time特徵非常重要。

Ri dge迴歸:

L2正則 化在擬合過程中通常都傾向於讓權值儘可能小,最後構造一個所有參數都比較小的模型,因爲一般認爲參數值小的模型比較簡單,能適應不同的數據集,也在一定程度上避免了過擬合現象。

非線 性模型

SVM
通過尋求結構化風險最小來提高學習機泛化能力,基本模型定義爲特徵空間上的間隔最大的線性分類器支持向量機的學習策略便是間隔最大化。

SVR用於標籤連續值的迴歸問題

SVC:用於分類標籤的分類問題

Boosting

一堆弱分類器的組合就可以成爲一個強分類器;不斷地在錯誤中學習,迭代來降低犯錯概率通過一系列的迭代來優化分類結果,每迭代一次引入一個弱分類器,來克服現在已經存在的弱分類器組合的短板。

Adaboost

整個訓練集上維護一個分佈權值向量W,用賦予權重的訓練集通過弱分類算法產生分類假設(基學習器)y(x), 然後計算錯誤率,用得到的錯誤率去更新分佈權值向量w,對錯誤分類的樣本分配更大的權值,正確分類的樣本賦予更小的權值,每次更新後用相同的弱分類算法產生新的分類假設,這些分類假設的序列構成多分類器,對這些多分類器用加權的方法進行聯合,最後得到決策結果

Gradient Boosting

迭代的時候選擇梯度下降的方向來保證最後的結果最好。損失函數用來描述模型的'靠譜'程度,假設模型沒有過擬合,損失函數越大,模型的錯誤率越高。如果我們的模型能夠讓損失函數持續的下降,最好的方式就是讓損失函數在其梯度方向下降。

GradientBoostingRegressor()
  • loss - 選擇損失函數,默認值爲ls(least squres),即最小二乘法,對函數擬合

  • learning_rate - 學習率

  • n_estimators - 弱學習器的數目,默認值100

  • max_depth - 每一個學習器的最大深度,限制迴歸樹的節點數目,默認爲3

  • min_samples_split - 可以劃分爲內部節點的最小樣本數,默認爲2

  • min_samples_leaf - 葉節點所需的最小樣本數,默認爲1


MLPRegressor()
參數詳解
  • hidden_layer_sizes - hidden_layer_sizes=(50, 50),表示有兩層隱藏層,第一層隱藏層有50個神經元,第二層也有50個神經元

  • activation - 激活函數 {‘identity’, ‘logistic’, ‘tanh’, ‘relu’},默認relu

  • identity - f(x) = x

  • logistic - 其實就是sigmod函數,f(x) = 1 / (1 + exp(-x))

  • tanh - f(x) = tanh(x)

  • relu - f(x) = max(0, x)

  • solver - 用來優化權重 {‘lbfgs’, ‘sgd’, ‘adam’},默認adam,

  • lbfgs - quasi-Newton方法的優化器:對小數據集來說,lbfgs收斂更快效果也 更好

  • sgd - 隨機梯度下降

  • adam - 機遇隨機梯度的優化器

  • alpha - 正則化項參數,可選的,默認0.0001

  • learning_rate - 學習率,用於權重更新,只有當solver爲’sgd’時使用

  • max_iter - 最大迭代次數,默認200

  • shuffle - 判斷是否在每次迭代時對樣本進行清洗,默認True,只有當solver=’sgd’或者‘adam’時使用

XGBRegressor 梯度提升迴歸樹,也叫梯度提升機

  • 採用連續的方式構造樹,每棵樹都試圖糾正前一棵樹的錯誤

  • 與隨機森林不同,梯度提升迴歸樹沒有使用隨機化,而是用到了強預剪枝

  • 而使得梯度提升樹往往深度很小,這樣模型佔用的內存少,預測的速度也快

from sklearn.linear_model import LinearRegression

from sklearn.svm import SVC

from sklearn.tree import DecisionTreeRegressor

from sklearn.ensemble import RandomForestRegressor

from sklearn.ensemble import GradientBoostingRegressor

from sklearn.neural_network import MLPRegressor

from xgboost.sklearn import XGBRegressor

from lightgbm.sklearn import LGBMRegressor


models = [LinearRegression(),

DecisionTreeRegressor(),

RandomForestRegressor(),

GradientBoostingRegressor(),

MLPRegressor(solver='lbfgs', max_iter=100),

XGBRegressor(n_estimators = 100, objective='reg:squarederror'),

LGBMRegressor(n_estimators = 100)]


result = dict()

for model in models:

model_name = str(model).split('(')[0]

scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error))

result[model_name] = scores

print(model_name + ' is finished')


result = pd.DataFrame(result)

result.index = ['cv' + str(x) for x in range(1, 6)]

result

各模型結果:

雖然隨機森林模型在此時取得較好的效果,但LGB的效果與其相差不大。對LGB進行調參後結果會得到提高,下面對LGB進行簡介。

LightGBM 使用的是histogram算法,佔用的內存更低,數據分隔的複雜度更低。思想是將連續的浮點特徵離散成k個離散值,並構造寬度爲k的Histogram。然後遍歷訓練數據,統計每個離散值在直方圖中的累計統計量。在進行特徵選擇時,只需要根據直方圖的離散值,遍歷尋找最優的分割點。
LightGBM採用leaf-wise生長策略:每次從當前所有葉子中找到分裂增益最大(一般也是數據量最大)的一個葉子,然後分裂,如此循環。因此同Level-wise相比,在分裂次數相同的情況下,Leaf-wise可以降低更多的誤差,得到更好的精度。

Leaf-wise的缺點是可能會長出比較深的決策樹,產生過擬合因此LightGBM在Leaf-wise之上增加了一個最大深度的限制,在保證高效率的同時防止過擬合。

參數:

  • num_leaves - 控制了葉節點的數目,它是控制樹模型複雜度的主要參數,取值應 <= 2 ^(max_depth)

  • bagging_fraction - 每次迭代時用的數據比例,用於加快訓練速度和減小過擬合

  • feature_fraction - 每次迭代時用的特徵比例,例如爲0.8時,意味着在每次迭代中隨機選擇80%的參數來建樹,boosting爲random forest時用

  • min_data_in_leaf - 每個葉節點的最少樣本數量。它是處理leaf-wise樹的過擬合的重要參數。將它設爲較大的值,可以避免生成一個過深的樹。但是也可能導致欠擬合

  • max_depth - 控制了樹的最大深度,該參數可以顯式的限制樹的深度

  • n_estimators - 分多少顆決策樹(總共迭代的次數)

  • objective - 問題類型

    • regression - 迴歸任務,使用L2損失函數

    • regression_l1 - 迴歸任務,使用L1損失函數

    • huber - 迴歸任務,使用huber損失函數

    • fair - 迴歸任務,使用fair損失函數

    • mape (mean_absolute_precentage_error) - 迴歸任務,使用MAPE損失函數

模型調參

常用的三種調參方法:
  • 心調參

  • GridSearchCV調

  • 貝葉斯調參

這裏給出一個模型可調參數及範圍選取的參考:

貪心 調參

拿當前對模型影響最大的參數調優,直到最優化;再拿下一個影響最大的參數調優,如此下去,直到所有的參 數調整完畢。這個方法的缺點就是可能會調到局部最優而不是全局最優,但是省時間省力,巨大的優勢面前,可以一試。

objectives = ["rank:map", "reg:gamma", "count:poisson", "reg:tweedie", "reg:squaredlogerror"]

max_depths = [1, 3, 5, 10, 15]

lambdas = [.1, 1, 2, 3, 4]



best_obj = dict()

for obj in objective:

model = LGBMRegressor(objective=obj)

score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))

best_obj[obj] = score

best_leaves = dict()

for leaves in num_leaves:

model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0], num_leaves=leaves)

score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))

best_leaves[leaves] = score

best_depth = dict()

for depth in max_depth:

model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0],

num_leaves=min(best_leaves.items(), key=lambda x:x[1])[0],

max_depth=depth)

score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))

best_depth[depth] = score

這裏 “count:poisson” 的損失最 小, 所以下個參數調試時會加上這個參數

GridSearchCV 調參

GridSearchCV,它存在的意義就是自動調參,只要把參數輸進去,就能給出最優化的結果和參數。但是這個方法適合於小數據集,一旦數據的量級上去了,很難得出結果。這個在這裏面優勢不大, 因爲數據集很大,不太能跑出結果,但是也整理一下,有時候還是很好用的。

parameters = {'objective': objective , 'num_leaves': num_leaves, 'max_depth': max_depth}

model = LGBMRegressor()

clf = GridSearchCV(model, parameters, cv=5)

clf = clf.fit(train_X, train_y)


clf.best_params_


model = LGBMRegressor(objective='regression',

num_leaves=55,

max_depth=15)


np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))

0.13626164479243302

貝葉斯調參

貝葉斯優化用於機器學習調參,主要思想是,給定優化的目標函數(廣義的函數,只需指定輸入和輸出即可,無需知道內部結構以及數學性質),通過不斷地添加樣本點來更新目標函數的後驗分佈(高斯過程,直到後驗分佈基本貼合於真實分佈。簡單的說,就是考慮了上一次參數的信息,從而更好的調整當前的參數。

與常規的網格搜索或者隨機搜索的區別是:

  • 貝葉斯調參採用高斯過程,考慮之前的參數信息,不斷地更新先驗;

  • 網格搜索未考慮之前的參數信息貝葉斯調參迭代次數少,速度快;網格搜索速度慢,參數多時易導致維度爆炸

  • 貝葉斯調參針對非凸問題依然穩健;網格搜索針對非凸問題易得到局部最優

使用方法:

  • 定義優化函數(rf_cv, 在裏面把優化的參數傳入,然後建立模型, 返回要優化的分數指標)

  • 定義優化參數

  • 開始優化(最大化分數還是最小化分數等)

  • 得到優化結果

from bayes_opt import BayesianOptimization

def rf_cv(num_leaves, max_depth, subsample, min_child_samples):

val = cross_val_score(

LGBMRegressor(objective = 'regression_l1',

num_leaves=int(num_leaves),

max_depth=int(max_depth),

subsample = subsample,

min_child_samples = int(min_child_samples)

),

X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)

).mean()

return 1 - val

rf_bo = BayesianOptimization(

rf_cv,

{

'num_leaves': (2, 100),

'max_depth': (2, 100),

'subsample': (0.1, 1),

'min_child_samples' : (2, 100)

}

)

rf_bo.maximize()


參考

【1】 Liner Regression-線性迴歸

【2】 Python實現決策樹

【3】 機器學習大殺器——梯度提升樹GBDT

【4】 靈魂拷問:你看過Xgboost原文嗎?

【5】 無痛看懂LightGBM原文

【6】 系列第一篇 「數據分析」之零基礎入門數據挖掘

【7】 系列第二篇 「特徵工程」之零基礎入門數據挖掘

完整項目實踐(共100多頁) 後臺回覆 數據挖掘電子版 獲取

原創不易點亮  在看 好不好

相關文章