XGBoost(一)简单机器学习示例

一、XGBoost简单应用

1
2
3
4
5
6
7
8
import xgboost as xgb

import numpy as np
import pandas as pd
import re
import os
import warnings
warnings.filterwarnings("ignore")
1
2
3
4
5
6
xgb_train = xgb.DMatrix("xgboost_source_code/demo/data/agaricus.txt.train")
xgb_test = xgb.DMatrix("xgboost_source_code/demo/data/agaricus.txt.test")
param = {'max_depth':2, 'eta':1, 'objective':'binary:logistic'}
num_round = 5
watch_list = [(xgb_train, "train"), (xgb_test, "test")]
model = xgb.train(param, xgb_train, num_round, watch_list)
[0]    train-error:0.04652    test-error:0.04283
[1]    train-error:0.02226    test-error:0.02173
[2]    train-error:0.00706    test-error:0.00621
[3]    train-error:0.01520    test-error:0.01800
[4]    train-error:0.00706    test-error:0.00621
1
2
preds = model.predict(xgb_test)
preds
array([0.08073306, 0.92217326, 0.08073306, ..., 0.98059034, 0.01182149,
       0.98059034], dtype=float32)

二、机器学习算法基础

我们首先介绍几个基础的机器学习算法的实现原理和应用,如KNN、线性回归、逻辑回归等,使读者对机器学习算法有一个基本认识的同时,了解如何在模型训练过程中进行优化,以及如何对模型结果进行评估。然后,对决策树模型做了详细介绍。决策树是XGBoost模型的重要组成部分,学习和掌握决策树的生成、剪枝等内容将会对后续的学习提供巨大帮助。排序问题是机器学习中的常见问题,神经网络和支持向量机也是经常采用的机器学习算法,最后将分别介绍两者的实现原理,结合详细的公式推导过程,使读者能够深入理解算法背后的数学原理。

1. KNN做鸢尾花数据预测

KNN的主要算法思想为:特征空间中的一个样本,如果与其最相似的k个样本中的大部分属于某个类别,则该样本也属于该类别。KNN既可以用于解决分类问题,也可以用于回归问题。

对于分类问题,离样本最近的k个邻居中占多数的类别作为该样本的类别,如果k=1,则选取最近邻居的类别作为该样本的类别。对于回归问题,样本的预测值是最近的k个邻居的平均值。

KNN的计算步骤如下。

  1. 计算测试样本与训练集中所有(或大部分)样本的距离,该距离可以是欧氏距离、余弦距离等,较常用的是欧氏距离。
  2. 找到步骤1中距离最短的k个样本,作为预测样本的邻居。
  3. 对于分类问题,通过投票机制选出k个邻居中最多的类别作为预测样本的预测值。对于回归问题,则采用k个邻居的平均值。

Iris也称鸢尾花卉数据集,是一类多重变量分析的数据集。数据集包含150个数据集,分为3类,每类50个数据,每个数据包含4个属性。可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。

数据描述

变量名 sepal_length sepal_width petal_length petal_width species
变量解释 花萼长度(单位cm) 花萼宽度(单位cm) 花瓣长度(单位cm) 花瓣宽度(单位cm) 种类
数据类型 numeric numeric numeric numeric categorical

1.1 导入数据集并观察分布

1
2
3
from matplotlib import pyplot
from sklearn.datasets import load_iris
iris = load_iris()
1
pd.DataFrame(iris.data, columns=iris.feature_names).head()
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setosa_sepal_len = iris.data[:50, 0]
setosa_sepal_width = iris.data[:50, 1]

versi_sepal_len = iris.data[50:100, 0]
versi_sepal_width = iris.data[50:100, 1]

vergi_sepal_len = iris.data[100:, 0]
vergi_sepal_width = iris.data[100:, 1]

pyplot.scatter(setosa_sepal_len, setosa_sepal_width, marker = 'o', c = 'b', s = 30, label = 'Setosa')
pyplot.scatter(versi_sepal_len, versi_sepal_width, marker = 'o', c = 'r', s = 50, label = 'Versicolour')
pyplot.scatter(vergi_sepal_len, vergi_sepal_width, marker = 'o', c = 'y', s = 35, label = 'Virginica')
pyplot.xlabel("sepal length")
pyplot.ylabel("sepal width")
pyplot.title("sepal length and width scatter")
pyplot.legend(loc = "upper right")

png

2. 绘制各个品种各个特征平均值的直方图

1
2
3
iris_data = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_data["class"] = iris.target
iris_data.head()
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) class
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0
1
2
3
4
5
6
from matplotlib import pyplot as plt
grouped_data = iris_data.groupby("class")
group_mean = grouped_data.mean()
group_mean.plot(kind='bar')
plt.legend(loc="center right", bbox_to_anchor=(1.4, 0.3), ncol=1)
plt.show()

png

3. 划分数据集

因为我们至少需要一个训练集来训练模型(KNN则用于最终预测计算),一个测试集来检验模型对新样本的预测能力,而目前只有一个数据集,因此需要对数据集进行划分。划分数据集有很多方法,比如留出法(hold-out)、交叉验证法等,本示例采用较常用的留出法。留出法的实现原理是,按照一定比例将数据集划分为互不相交的两部分,分别作为训练集和测试集。此处选用的训练集、测试集的比例为4:1,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
msk = np.random.rand(len(iris_data)) < 0.8
train_data_origin = iris_data[msk]
test_data_origin = iris_data[~msk]

train_data = train_data_origin.reset_index(drop=True)
test_data = test_data_origin.reset_index(drop=True)

train_label = train_data["class"]
test_label = test_data["class"]

train_fea = train_data.drop("class", 1)
test_fea = test_data.drop("class", 1)
1
2
3
# 数据归一化
train_norm = (train_fea - train_fea.min()) / (train_fea.max() - train_fea.min())
test_norm = (test_fea - test_fea.min()) / (test_fea.max() - test_fea.min())
1
2
3
4
5
6
7
from sklearn import neighbors
from sklearn.metrics import accuracy_score
knn = neighbors.KNeighborsClassifier(n_neighbors=3)
knn.fit(train_norm, train_label)
predict = knn.predict(test_norm)
accuracy = accuracy_score(test_label, predict)
accuracy
0.9166666666666666

KNN算法是机器学习中最简单、有效的算法。上面通过鸢尾花品种分类的示例详细介绍了KNN算法的实现原理和应用。KNN算法属于懒惰学习算法,当数据集的样本容量比较大时,计算量也会比较大,并且需要较大的存储空间。此外,它无法给出数据的任何基础结构信息,后面介绍的算法将会解决这个问题。

2. 线性回归预测波士顿房价

下面通过一个示例来说明如何应用线性回归。以波士顿房屋价格数据集作为示例数据集,该数据集包含了波士顿房屋以及周边环境的一些详细信息,包括城镇人均犯罪率、一氧化碳浓度、住宅平均房屋数等。该数据集包含506个样本、13个特征字段、1个label字段

数据描述

No 属性 数据类型 字段描述
1 CRIM Float 城镇人均犯罪率
2 ZN Float 占地面积超过2.5万平方英尺的住宅用地比例
3 INDUS Float 城镇非零售业务地区的比例
4 CHAS Integer 查尔斯河虚拟变量 (= 1 如果土地在河边;否则是0)
5 NOX Float 一氧化氮浓度(每1000万份)
6 RM Float 平均每居民房数
7 AGE Float 在1940年之前建成的所有者占用单位的比例
8 DIS Float 与五个波士顿就业中心的加权距离
9 RAD Integer 辐射状公路的可达性指数
10 TAX Float 每10,000美元的全额物业税率
11 PTRATIO Float 城镇师生比例
12 B Float 1000(Bk - 0.63)^ 2其中Bk是城镇黑人的比例
13 LSTAT Float 人口中地位较低人群的百分数
14 MEDV Float (目标变量/类别属性)以1000美元计算的自有住房的中位数
1
2
3
4
from sklearn.datasets import load_boston
boston = load_boston()
X = boston.data
y = boston.target
1
2
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
1
2
3
4
5
6
7
8
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_squared_error

lr = LinearRegression()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)
mean_squared_error(y_test, y_pred)
23.380836480270315

另外XGBoost也提供了线性回归的API,其数据加载步骤与上述scikit-learn的方法相同,不再赘述。使用XGBoost,首先要把数据转化为其自定义的DMatrix格式,该格式为XGBoost特定的输入格式。然后定义模型参数,此处定义较为简单,只选用了2个参数,如下:

1
params = {"objective":"reg:linear", "booster":"gblinear"}

其中,objective用于确定模型的目标函数,这里以reg:squarederror作为目标函数。参数booster用于确定采用什么样的模型,此处选择的是线性模型(gblinear),读者也可根据应用场景选择其他模型(gbtree、dart),因本节主要介绍线性回归,因此选用线性模型。定义好参数后即可训练模型,最后用该模型对测试集进行预测。代码如下:

1
2
3
4
train_xgb = xgb.DMatrix(X_train, y_train)
params = {"objective":"reg:squarederror", "booster":"gblinear"}
model = xgb.train(dtrain=train_xgb, params=params)
y_pred = model.predict(xgb.DMatrix(X_test))

综上,线性回归是一种解决回归问题的常见方法。在线性回归中,求解最优参数的方法是最小化其损失函数。最小化损失函数有两种方法:正规方程和梯度下降法

正规方程通过矩阵运算求得最优参数,但其必须满足$X^TX$可逆,当样本数比特征数还少时,$X^TX$的逆是不能直接计算的。

梯度下降法是沿负梯度的方向一步步最小化损失函数,求解最优参数。梯度下降法需要指定步长并进行多次迭代,但相比于正规方程,梯度下降法可以应用于特征数较大的情况。最后,通过波士顿房价的示例展示了通过scikit-learn和XGBoost如何应用线性回归。

3. 逻辑回归预测良性/恶性乳腺肿瘤

下面将使用逻辑回归预测乳腺肿瘤是良性的还是恶性的。示例采用的数据集为威斯康星诊断乳腺癌数据集,它通过细胞核的相关特征来预测乳腺肿瘤为良性/恶性,这是一个非常著名的二分类数据集。该数据集包含569个样本,其中有212个恶性肿瘤样本,357个良性肿瘤样本。共有32个字段,字段1为ID,字段2为label,其他30个字段为细胞核的相关特征,例如:

  • 半径(从中心到周边点的平均距离)
  • 纹理(灰度值的标准偏差)
  • 周长
  • 面积
  • 光滑度(半径长度的局部变化)
  • 紧凑性(周长的二次方/面积的负一次方)
  • 凹度(轮廓的凹陷程度)
  • 凹点(轮廓中凹部的数量)
  • 对称
  • 分形维数

对于每张图像,分别计算以上10个特征的平均值、标准误差和最差/最大(最大的3个值的平均)值,由此生成30个特征。例如,字段3表示平均半径,字段13表示半径的标准误差,字段23表示最差半径。所有特征都保留4位有效数字。

scikit-learn已经集成了该数据集,并进行了相应的处理(如去掉了ID字段),使用时直接加载即可,如下:

1
2
3
4
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target

其中,X为特征数据,包含上面介绍的30个特征,y为标签数据,标记乳腺肿瘤类型,1代表良性,0代表恶性。下面按4:1的比例将数据集划分为训练集和测试集:

1
2
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
1
2
3
4
5
6
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
lr = LogisticRegression()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)
print(classification_report(y_test, y_pred, target_names=["Benign", "Malignant"]))
              precision    recall  f1-score   support

      Benign       0.95      0.90      0.93        42
   Malignant       0.95      0.97      0.96        72

    accuracy                           0.95       114
   macro avg       0.95      0.94      0.94       114
weighted avg       0.95      0.95      0.95       114

其中,列表的左边一列为分类的标签名,avg/total为各列的均值。support表示该类别样本出现的次数。

XGBoost提供了逻辑回归的API,读者可以通过XGBoost中的逻辑回归对数据集进行预测,代码如下:

1
2
3
4
train_xgb = xgb.DMatrix(X_train, y_train)
params = {"objective":"reg:logistic", "booster":"gblinear"}
model = xgb.train(dtrain=train_xgb, params=params)
y_pred = model.predict(xgb.DMatrix(X_test))

XGBoost逻辑回归API的调用方式和线性回归类似,唯一不同的是目标函数objective改为reg:logisticbooster仍然选择线性模型。

注意,XGBoost在预测结果上和scikit-learn有些差别,XGBoost的预测结果是概率,而scikit-learn的预测结果是0或1的分类(scikit-learn也可通过predict_proba输出概率)。在XGBoost中,如果需要输出0或1的分类,需要用户自己对其进行转化,例如:

1
2
3
4
ypred_bst = np.array(y_pred)
y_pred_bst = ypred_bst > 0.5
y_pred_bst = y_pred_bst.astype(int)
print(classification_report(y_test, y_pred_bst, target_names=["Benign", "Malignant"]))
              precision    recall  f1-score   support

      Benign       0.90      0.67      0.77        42
   Malignant       0.83      0.96      0.89        72

    accuracy                           0.85       114
   macro avg       0.87      0.81      0.83       114
weighted avg       0.86      0.85      0.84       114

4. 决策树解决肿瘤分类问题

scikit-learn实现了决策树算法,它采用的是一种优化的CART版本,既可以解决分类问题,也可以解决回归问题。分类问题使用DecisionTreeClassifier类,回归问题使用DecisionTreeRegressor类。两个类的参数相似,只有部分有所区别,以下是对主要参数的说明。

  1. criterion:特征选择采用的标准。DecisionTreeClassifier分类树默认采用gini(基尼系数)进行特征选择,也可以使用entropy(信息增益)。DecisionTreeRegressor默认采用MSE(均方误差),也可以使用MAE(平均绝对误差)。
  2. splitter:节点划分的策略。支持bestrandom两种方式,默认为best,即选取所有特征中最优的切分点作为节点的分裂点,random则随机选取部分切分点,从中选取局部最优的切分点作为节点的分裂点。
  3. max_depth:树的最大深度,默认为None,表示没有最大深度限制。节点停止分裂的条件是:样本均属于相同类别或所有叶子节点包含的样本数量小于$min_samples_split$。若将该参数设置为None以外的其他值,则决策树生成过程中达到该阈值深度时,节点停止分裂。
  4. min_samples_split:节点划分的最小样本数,默认为2。若节点包含的样本数小于该值,则该节点不再分裂。若该字段设置为浮点数,则表示最小样本百分比,划分的最小样本数为$ceil(min_samples_split*n_samples)$。
  5. min_samples_leaf:叶子节点包含的最小样本数,默认为1。此字段和min_samples_split类似,取值可以是整型,也可以是浮点型。整型表示一个叶子节点包含的最小样本数,浮点型则表示百分比。叶子节点包含的最小样本数为$ceil(min_samples_leaf*n_samples)$。
  6. max_features :划分节点时备选的最大特征数,默认为None,表示选用所有特征。若该字段为整数,表示选用的最大特征数;若为浮点数,则表示选用特征的最大百分比。最大特征数为$int(max_features*n_features)$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.metrics import classification_report
from sklearn import tree

cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=8)

clf = tree.DecisionTreeClassifier(max_depth=4)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred, target_names=["Benign", "Malignant"]))
              precision    recall  f1-score   support

      Benign       0.95      0.87      0.91        46
   Malignant       0.92      0.97      0.94        68

    accuracy                           0.93       114
   macro avg       0.93      0.92      0.93       114
weighted avg       0.93      0.93      0.93       114

为便于读者直观地理解树模型,可以使用Graphviz工具包将模型可视化。Graphviz是一个开源的图形可视化软件,可以将结构数据转化为形象的图形或网络,在软件工程、数据库、机器学习等领域的可视化界面中有应用。函数export_graphviz可以将scikit-learn中的决策树导出为Graphviz的格式,导出完成后即可对Graphviz格式的决策树进行图形渲染,如下:

1
2
3
4
5
6
7
8
import graphviz
dot_data = tree.export_graphviz(clf, out_file=None,
feature_names=cancer.feature_names,
class_names=cancer.target_names,
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
# graph
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 将dot_data写入到txt文件中
f = open('output/img/dot_data.txt', 'w')
f.write(dot_data)
f.close()

# 解决中文乱码问题
# import re
# f_old = open('dot_data.txt', 'r')
# f_new = open('dot_data_new.txt', 'w', encoding='utf-8')
# for line in f_old:
# if 'fontname' in line:
# font_re = 'fontname=(.*?)]'
# old_font = re.findall(font_re, line)[0]
# line = line.replace(old_font, 'SimHei')
# f_new.write(line)
# f_old.close()
# f_new.close()

5. 神经网络识别手写体数字

手写体数字数据集(MNIST)是一个经典的多分类数据集,由不同的手写体数字图片以及0~9的数字标签样本构成。scikit-learn中的手写体数字数据集共有1797个样本,每个样本包含一个8×8像素的图像和0~9的数字标签。scikit-learn通过MLPClassifier类实现的多层感知器完成分类任务,通过MLPRegressor类完成回归任务。对于手写体数字数据集这样的多分类问题,显然要采用MLPClassifierMLPClassifier的常用参数如下:

  • hidden_layer_sizes:用来指定隐藏层包含的节点数量,其类型为tuple,长度是n_layers-2,其中n_layers为网络总层数;
  • activation:指定隐藏层的激活函数,默认为relu;
  • solver:指定权重的更新方法,默认为sgd,即随机梯度下降法;
  • alpha:指定L2正则的惩罚系数;
  • learning_rate:指定训练过程中学习率更新方法,有constant、invscaling和adaptive这3种方法。其中,constant表示学习率在训练过程中为固定值;invscaling表示随着训练的进行,学习率指数降低;adaptive表示动态调整,当训练误差不断减少时(减少量超过一定阈值),学习率保持不变,若连续两次迭代训练损失未达到上述条件,则学习率缩小为原值的1/5。
  • max_iter表示迭代的最大轮数,对于solver为sgd和adam的情况,max_iter相当于epoch的数量。

了解了MLPClassifier类的常用参数后,下面介绍如何使用MLPClassifier来解决识别手写体数字的问题。

1
2
3
4
5
from sklearn.datasets import load_digits
digits = load_digits()
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=8)
1
2
3
4
5
6
7
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score

mlp = MLPClassifier(hidden_layer_sizes=(128, 64), max_iter=50, alpha=1e-4, solver='sgd')
mlp.fit(X_train, y_train)
y_pred = mlp.predict(X_test)
print("Accuracy: ", accuracy_score(y_test, y_pred))
Accuracy:  0.9555555555555556

6. 支持向量机识别手写体数字

下面仍以手写体数字数据集(MNIST)为例,介绍如何使用SVM解决分类问题。SVM既可以解决二分类问题,也能解决多分类问题。SVM解决多分类问题的方法主要有两种:one-vs-one和one-vs-the-rest。

  • one-vs-one为每两类样本建立一个二分类器,则$k$个类别的样本需要建立$\frac{k(k-1)}{2}$个二分类器。
  • one-vs-the-rest是为每个类别和其他剩余类别建立一个二分类器,从中选择预测概率最大的分类作为最终分类,k个类别的样本需建立k个二分类器。

scikit-learn通过SVC类来解决分类问题,通过SVR类来解决回归问题(SVM也可以解决回归问题),下面采用SVC类解决手写体数字识别的多分类问题。

SVC可以通过参数kernel指定采用的核函数,支持的核函数有:linear(线性核函数)、poly(多项式)、rbf(高斯)、sigmoidprecomputed以及自定义形式callable。若不指定kernel,其默认采用rbf。SVC还有几个比较常用的参数:

  • 惩罚参数$C$,即前面松弛变量中介绍的不满足约束条件样本的惩罚系数;
  • 参数degree是多项式核函数(kernel设置为poly)的阶数;
  • 参数gamma表示高斯核和sigmoid核中的内核系数,在高斯核中对应的是高斯核函数公式中的$\frac{1}{2\sigma^2}$。

数据集的加载和划分同神经网络中的示例,不再赘述。此处主要介绍模型拟合与评估。

先定义一个SVC模型,这里采用高斯核函数,惩罚系数C为1.0,gamma为0.001,当然也可以通过参数调优来确定参数。定义模型之后即可训练模型,然后对测试集进行预测,最后以准确率为指标评估预测结果。具体代码如下:

1
2
3
4
5
6
7
from sklearn import svm
from sklearn.metrics import accuracy_score

svc = svm.SVC(C=1.0, kernel="rbf", gamma=0.001)
svc.fit(X_train, y_train)
y_pred = svc.predict(X_test)
print("Accuracy", accuracy_score(y_test, y_pred))
Accuracy 0.9861111111111112

也可以采用其他核函数,如多项式核函数,代码如下:

1
2
3
4
svc = svm.SVC(C=1.0, kernel="poly", degree=3)
svc.fit(X_train, y_train)
y_pred = svc.predict(X_test)
print("Accuracy", accuracy_score(y_test, y_pred))
Accuracy 0.9861111111111112

可以看到,在本例中采用多项式核函数和高斯核函数的预测准确率是相同的。读者也可自行尝试其他参数,观察不同参数对模型预测的影响。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2020-2021 chenk
  • 由 帅气的CK本尊 强力驱动
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信