R と Python で XGBoost (eXtreme Gradient Boosting) を試してみたのでメモ。
Boosting
バギング (Bootstrap aggregating; bagging) が弱学習器を独立的に学習していくのに対して, ブースティング (Boosting) は弱学習器の学習を逐次的に行います。
各ステップごとに前の弱学習器の結果を参考にしながら新たな弱学習器の重みを変化させ間違ったものをうまく識別できるように損失関数を最小化します。学習速度はバギングに比べ直列性のため遅くなる傾向があります。
バギングの代表例は ランダムフォレスト (Random Forest), ブースティングの代表例は AdaBoost [1] で, 重みを適応的 (adaptive) に更新していく手法 [2]です。OpenCVのオブジェクト検出で使われています。
オブジェクト検出では画像の局所的な部分における明暗関係などの特徴量について, 弱識別器を作り評価値が高い (エラー率の低い) 識別器とその時の重みを採用し, サンプルを更新していきます。最後に弱識別器の出力に α で重み付けして, 結合することで最終的な識別器とします。この学習器を使って, 検出ウィンドウのラスタスキャンで入力をずらしていき, 識別器を通した出力が閾値を超えた時に検出とします。
Gradient Boosting
Gradient Boosting は Boosting の一種で, 損失関数の勾配を用います。詳細は Gradient boosting machines, a tutorial [Natekin and Knoll, 2013] が参考になります。
XGBoost
XGBoost (eXtreme Gradient Boosting) [3] は勾配ブースティングのC++実装で, 大規模データに対してもスケールできるように高速化の工夫がされています。
XGBoostインストール
環境はOSX 10.10.2, R 3.1です。
現時点では CRAN に {xgboost} パッケージが見つからなかったため source からインストールします。
$ git clone https://github.com/dmlc/xgboost.git
$ R
> install.packages("devtools")
> require(devtools)
> install_local('xgboost/', subdir = 'R-package')
RでXGBoostを試してみる
定番のirisデータセットで XGBoost を試してみます。
require(xgboost)
# k-fold
data(iris)
num <- sample(nrow(iris), 150)
k <- 2
data <- split(iris[num,], 1:k)
model <- xgboost(
params=list(objective="multi:softmax"),
eval_metric="mlogloss",
num_class=3,
data=as.matrix(data$"1"[,1:4]),
label=as.integer(data$"1"[,5])-1,
nrounds=500
)
pred <- predict(model, as.matrix(data$"2"[,1:4]))
table(as.integer(data$"2"[,5])-1, pred)
# pred
# 0 1 2
# 0 28 0 0
# 1 0 24 3
# 2 0 2 18
sum(ifelse(as.integer(data$"2"[,5])-1 == pred, 0, 1)) / (nrow(iris)/k)
# [1] 0.06666667
imp <- xgb.importance(names(iris), model=model)
print(imp)
# Feature Gain Cover Frequence
# 1: Petal.Length 0.882104244 0.52692315 0.44000000
# 2: Petal.Width 0.103582149 0.25266661 0.24153846
# 3: Sepal.Width 0.008033523 0.06575587 0.09384615
# 4: Sepal.Length 0.006280085 0.15465438 0.22461538
Code はGitHubにあります。
PythonでXGBoostを試してみる
PythonでXGBoostを使うには XGBoost Python Package を参考にインストールします。手っ取り早く使いたかったので pip のヴァージョン指定でインストールしました。
$ pip install xgboost==0.4a30
# Updateの場合は以下
# $ pip install -U pip
# $ sudo pip install -U xgboost
# $ pip list | grep xgb
# xgboost (0.6a2)
定番の iris から。
# -*- coding: utf-8 -*-
import numpy as np
import scipy as sp
import xgboost as xgb
from sklearn import datasets
from sklearn.metrics import confusion_matrix
from sklearn.grid_search import GridSearchCV
from sklearn.grid_search import RandomizedSearchCV
iris = datasets.load_iris()
train_x = iris.data[0::2, :]
train_y = iris.target[0::2]
test_x = iris.data[1::2, :]
test_y = iris.target[1::2]
np.random.seed(131)
# Grid Search
params = {'learning_rate': [0.05, 0.1],
'max_depth': [3, 5],
'subsample': [0.9, 0.95],
'colsample_bytree': [0.5, 1.0]
}
clf = xgb.XGBClassifier()
cv = GridSearchCV(clf,
params,
cv=10,
scoring="log_loss",
n_jobs=1,
verbose=2)
cv.fit(train_x, train_y)
predict = cv.predict(test_x)
print confusion_matrix(test_y, predict)
print cv.best_estimator_
best_param = {'learning_rate': cv.best_estimator_.learning_rate,
'max_depth': cv.best_estimator_.max_depth,
'gamma': cv.best_estimator_.gamma,
'silent': cv.best_estimator_.silent,
'objective': cv.best_estimator_.objective,
'num_class': 3
}
dtrain = xgb.DMatrix(np.array(train_x), label=train_y)
num_round = 10
bst = xgb.train(best_param, dtrain, num_round)
bst.save_model('./xgb/iris01.model')
irisは分類問題だったので classifier を使いましたが, 回帰問題では regressor を使うことができます。
Kaggle の Let’s try to run gs on xgb regressor (Apache 2.0 open source license.)を参考に試してみます。
上記では, スコアリングにジニ係数を用いています。
import numpy as np
import scipy as sp
import pandas as pd
import xgboost as xgb
from sklearn import pipeline, metrics, grid_search
def gini(y_true, y_pred):
""" Simple implementation of the (normalized) gini score in numpy.
Fully vectorized, no python loops, zips, etc. Significantly
(>30x) faster than previous implementions
Credit: https://www.kaggle.com/jpopham91/
"""
# check and get number of samples
assert y_true.shape == y_pred.shape
n_samples = y_true.shape[0]
# sort rows on prediction column
# (from largest to smallest)
arr = np.array([y_true, y_pred]).transpose()
true_order = arr[arr[:,0].argsort()][::-1,0]
pred_order = arr[arr[:,1].argsort()][::-1,0]
# get Lorenz curves
L_true = np.cumsum(true_order) / np.sum(true_order)
L_pred = np.cumsum(pred_order) / np.sum(pred_order)
L_ones = np.linspace(0, 1, n_samples)
# get Gini coefficients (area between curves)
G_true = np.sum(L_ones - L_true)
G_pred = np.sum(L_ones - L_pred)
# normalize to true Gini coefficient
return G_pred/G_true
def normalized_gini(y_true, y_pred):
ng = gini(y_true, y_pred)/gini(y_true, y_true)
return ng
def fit(train, target):
# set up pipeline
est = pipeline.Pipeline([
('xgb', xgb.XGBRegressor(silent=True)),
])
# create param grid for grid search
params = {
'xgb__learning_rate': [0.05, 0.1, 0.3, ],
'xgb__min_child_weight': [5, 6, 7, ],
'xgb__subsample': [0.5, 0.7, 0.9, 0.95,],
'xgb__colsample_bytree': [0.5, 0.7, 0.9, ],
'xgb__max_depth': [1, 3, 5, 7, 9, 11, ],
'xgb__n_estimators': [10, 50, 100, ],
}
# set up scoring mechanism
gini_scorer = metrics.make_scorer(normalized_gini, greater_is_better=True)
# initialize gridsearch
gridsearch = grid_search.RandomizedSearchCV(
estimator=est,
param_distributions=params,
scoring=gini_scorer,
verbose=10,
n_jobs=-1,
cv=10,
n_iter=5,
)
# fit gridsearch
gridsearch.fit(train, target)
print('Best score: %.3f' % gridsearch.best_score_)
print('Best params:')
for k, v in sorted(gridsearch.best_params_.items()):
print("\t%s: %r" % (k, v))
# get best estimator
return gridsearch.best_estimator_
def predict(est, testX):
return est.predict(testX)
def save(predY, testY):
diff = predY - testY
pred = pd.DataFrame({'predict': predY, 'actual': testY, 'diff': diff})
pred.to_csv('../data/xgb.csv')
print('MSE: %.3f' % np.mean(np.absolute(diff)))
print('Predictions saved to xgb.csv')
if __name__ == '__main__':
np.random.seed(131)
# load data
df = pd.read_csv("data.csv", header=0)
# create dataset
trainX = data[0:800,[4,5,6,7,9,10,11,12]]
trainY = data[0:800,8]
testX = data[801:1198,[4,5,6,7,9,10,11,12]]
testY = data[801:1198,8]
# fit model
est = fit(trainX, trainY)
predY = predict(est, testX)
save(predY, testY)
[1] A Decision-Theoretic Generalization of on-Line Learning and an Application to Boosting [Freund and Schapire, 1995]
[2] 出力が離散値の場合は, Discrete AdaBoost, 実数値の場合は Real AdaBoost。実数の場合は重み α は実質的に要らなくなる。
[3] XGBoost: A Scalable Tree Boosting System [Chen and Guestrin, 2016]
[4] 勾配ブースティングについてざっくりと説明する