机器学习实战-ShowMeAI--全-
机器学习实战(ShowMeAI)(全)
机器学习实战:手把手教你玩转机器学习系列

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
本篇内容是ShowMeAI组织的「Python 机器学习实战」系列教程入口,本教程尽量以案例和代码驱动的方式,帮助大家学习机器学习算法应用流程和各个链条环节,掌握构建场景建模解决方案并进行效果调优的能力。(想深入理解涉及的机器学习算法原理的同学,可以关注ShowMeAI的另外一个系列图解机器学习算法)
内容覆盖:机器学习工具库安装与环境配置、机器学习流水线、数据预处理、特征工程、scikit-learn 工具应用、xgboost 应用详解、Lightgbm 应用详解、自动化特征工程与自动化机器学习建模等。
教程地址
点击查看完整教程学习路径
内容章节
1.Python 机器学习算法应用实践

2.SKLearn 入门与简单应用案例

3.SKLearn 最全应用指南

4.XGBoost 建模应用详解

5.LightGBM 建模应用详解

6.Python 机器学习综合项目-电商销量预估

7.Python 机器学习综合项目-电商销量预估<进阶方案>

8.机器学习特征工程最全解读

9.自动化特征工程工具 Featuretools 应用

10.AutoML 自动化机器学习建模

ShowMeAI 系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
- 深度学习教程 | 吴恩达专项课程 · 全套笔记解读

机器学习实战 | Python 机器学习算法应用实践

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/201
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
本篇文章希望带大家完整走一遍机器学习应用流程,我们会讲解到基于 Python 的机器学习算法,应用在结构化数据和非结构化数据(图像)上,希望通过文章内容帮助大家在案例中重温机器学习基础知识,并学习应用机器学习解决问题的基本流程。
文章中会用到下述两个库来实现机器学习算法:
- Scikit-Learn:最常用的 python 机器学习算法工具库之一。
- Keras:便捷的深度学习神经网络搭建应用工具库。
对于上述两个工具库的用法,大家也可以通过ShowMeAI的文章 AI 建模工具速查 | Scikit-Learn 使用指南 和 AI 建模工具速查 | Keras 使用指南 来快速查询和使用。
在本篇文章中,我们将讲解到以下内容:

- 问题抽象与理解
- 数据准备与处理(预处理、特征提取、特征工程等)
- 各种机器学习算法
- 实验结果分析与对比
- 模型选择与调优
我们会覆盖到的机器学习算法包括:KNN、朴素贝叶斯、逻辑回归、SVM、决策树、随机森林、感知机、前馈神经网络、卷积神经网络。

1.环境准备
工欲善其事必先利其器,我们先安装一下必需的 Python 库(当然我们也推荐大家用集成环境 anaconda,具体的安装与设置可以参考ShowMeAI文章 图解 Python | 安装与环境设置 完成):
- Numpy:用于 Python 的科学计算。
- 相关重点知识请查看 数据科学工具速查 | Numpy 使用指南 或者 图解数据分析:从入门到精通系列教程 中的 Numpy 详解教程。
- PIL:一个简单的图像处理库。
- Scikit-Learn:包含多种机器学习算法。
- 相关重点知识请查看 AI 建模工具速查 | Scikit-Learn 使用指南
- Kears 和 TensorFlow:用于深度学习。本教程可以仅采用 CPU 版本的 TensorFlow。
- OpenCV:本教程并不直接使用 OpenCV,但 imutils 库依赖它。
- 相关重点知识请查看 AI 建模工具速查 | OpenCV 使用指南
- imutils:图像处理/计算机视觉库。
可以采用 pip 安装,命令如下:
$ pip install numpy
$ pip install pillow
$ pip install --upgrade scikit-learn
$ pip install tensorflow # or tensorflow-gpu
$ pip install keras
$ pip install opencv-contrib-python
$ pip install --upgrade imutils
2.数据集
因为本篇文章我们介绍结构化数据和非结构化数据的不同建模,我们这里用到两个数据集。
2.1 iris(鸢尾花)数据集
第一个数据集是 iris(鸢尾花)数据集,它是一个入门级数据集。整个数据集都是数值型的数据,是一个结构化的表格数据,每一行代表一个样本,然后每一列就是不同的属性。

这个数据集主要是收集了三种不同的鸢尾花的数据,分别为:
- Iris Setosa
- Iris Versicolor
- Iris Virginica
对应图中最后一列Class label,然后还有四种属性,分别是:
- Sepal length:萼片长度
- Sepal width:萼片宽度
- Petal length:花瓣长度
- Petal width:花瓣宽度
对于该数据集,我们的目标就是根据给定的四个属性,训练一个机器学习模型来正确分类每个样本的类别,这是一个典型的分类任务。
2.2 图像数据集
第二个数据集是一个图像数据集。它包括海岸线(Coast)、森林(Forest)和高速公路(Highway)三种场景,总共是 948 张图片,我们需要构建模型完成类别的分类,每个类别的具体图片数量如下:
- 海岸线(Coast):360
- 森林(Forest):328
- 高速公路(Highway):260

3.机器学习应用步骤
我们在不同场景下应用机器学习算法,都有大致的步骤,比如下面是一个典型的机器学习应用流程:

当然,并不是其中的每一步都是必须的,我们也可能会调整其中某些步骤中的细节。
3.1 问题抽象与理解
针对我们的问题,问一下自己:
- 数据集是哪种类型?数值型,类别型还是图像?
- 模型的最终目标是什么?
- 如何定义和衡量“准确率”呢?
- 以目前自身的机器学习知识来看,哪些算法在处理这类问题上效果很好?
前序问题比较简单,最后的问题,随着大家应用机器学习解决问题的经验积累,可以更准确快速地回答。
3.2 数据准备与处理
数据准备与处理,包括数据预处理以及特征工程了。一般这一步,包括了加载数据、检查数据、探索性数据分析(EDA)、数据预处理,进而决定需要做的特征提取或者特征工程。

特征提取是应用某种算法通过某种方式来量化数据的过程。比如,对于图像数据,我们可以采用计算直方图的方法来统计图像中像素强度的分布,通过这种方式,我们就得到描述图像颜色的特征。
特征工程是将原始输入数据转换成一个更好描述潜在问题的特征表示的过程。大家可以查看ShowMeAI的 机器学习专题文章 系统了解特征工程的常见方法。
3.3 多模型应用
下一步可以选择各种候选机器学习算法,并应用在数据集上。我们安装的工具包内,包含很多机器学习算法,比如下述模型都可以用作分类:
- 线性模型(逻辑回归、线性 SVM)
- 非线性模型(RBF、SVM、梯度下降分类器)
- 树和基于集成的模型(决策树、随机森林)
- 神经网络(多层感知机、卷积神经网络)
对于模型选择,当然很多需要依据实验效果来定,但我们也有一些先序的经验,比如:
- 对于稠密型多特征的数据集,随机森林算法的效果很不错;
- 逻辑回归算法可以很好处理高维度的稀疏数据;
- 对于图像数据,CNNs 的效果非常好。
下图为 scikit-learn 工具库 官方给的一个模型选择思路参考:

4.构建机器学习流程并实验分析
我们构建如下的代码文件目录,包含四个代码文件和一个3scenes图像文件夹(内含三场景数据集),iris 数据集无需另外存储,直接采用scikit-learn库载入即可。
├── 3scenes
│ ├── coast [360 entries]
│ ├── forest [328 entries]
│ └── highway [260 entries]
├── iris_classifier.py
├── image_classifier.py
├── nn_iris.py
└── basic_cnn.py
4.1 结构化数据建模
首先实现iris_classifier,我们这里直接使用 sklearn 的机器学习算法来对iris数据集进行分类。
# 导入需要的库
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import load_iris
import argparse
# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", type=str, default="knn", help="type of python machine learning model to use")
args = vars(ap.parse_args())
# 定义一个保存模型的字典,根据 key 来选择加载哪个模型
models = {
"knn": KNeighborsClassifier(n_neighbors=1),
"naive_bayes": GaussianNB(),
"logit": LogisticRegression(solver="lbfgs", multi_class="auto"),
"svm": SVC(kernel="rbf", gamma="auto"),
"decision_tree": DecisionTreeClassifier(),
"random_forest": RandomForestClassifier(n_estimators=100),
"mlp": MLPClassifier()
}
其中,models 从前往后依次包括这些算法:KNN、朴素贝叶斯、逻辑回归、SVM、决策树、随机森林、感知机等。
我们直接调用sklearn中相应的函数来实现对应的算法即可,这里直接用一个models的字典来保存不同模型的初始化,然后根据参数--model来调用对应的模型,比如命令输入python iris_classifier.py --model knn就是调用knn算法模型。
接着就是载入数据部分:
print("加载数据中...")
dataset = load_iris()
trainX, testX, trainY, testY = train_test_split(dataset.data, dataset.target, random_state=3, test_size=0.2)
这里直接调用sklearn.datasets中的load_iris()载入数据,然后采用train_test_split来划分训练集和数据集,这里是 80%数据作为训练集,20%作为测试集。
最后就是训练模型和预测部分:
# 训练模型
print("应用 '{}' 模型建模...".format(args["model"]))
model = models[args["model"]]
model.fit(trainX, trainY)
# 预测并输出一份分类结果报告
print("评估模型效果...")
predictions = model.predict(testX)
print(classification_report(testY, predictions, target_names=dataset.target_names))
4.2 图像数据建模

类似的过程对三场景图像数据集构建代码image_classifier.py:
# 导入工具库
from sklearn.preprocessing import LabelEncoder
from PIL import Image
from imutils import paths
import numpy as np
import os
其中LabelEncoder是为了将标签从字符串编码为整型,然后其余几项都是处理图像相关。
对于图像数据,如果直接采用原始像素信息输入模型中,大部分的机器学习算法效果都很不理想,所以这里采用特征提取方法,主要是统计图像颜色通道的均值和标准差信息,总共是RGB3 个通道,每个通道各计算均值和标准差,然后结合在一起,得到一个六维的特征,函数如下所示:
def extract_color_stats(image):
'''
将图片分成 RGB 三通道,然后分别计算每个通道的均值和标准差,然后返回
:param image:
:return:
'''
(R, G, B) = image.split()
features = [np.mean(R), np.mean(G), np.mean(B), np.std(R), np.std(G), np.std(B)]
return features
然后同样会定义一个models字典,代码一样,这里就不贴出来了,然后图像载入部分的代码如下:
# 加载数据并提取特征
print("抽取图像特征中...")
imagePaths = paths.list_images(args['dataset'])
data = []
labels = []
# 循环遍历所有的图片数据
for imagePath in imagePaths:
# 加载图片,然后计算图片的颜色通道统计信息
image = Image.open(imagePath)
features = extract_color_stats(image)
data.append(features)
# 保存图片的标签信息
label = imagePath.split(os.path.sep)[-2]
labels.append(label)
# 对标签进行编码,从字符串变为整数类型
le = LabelEncoder()
labels = le.fit_transform(labels)
# 进行训练集和测试集的划分,80%数据作为训练集,其余 20%作为测试集
trainX, testX, trainY, testY = train_test_split(data, labels, test_size=0.2)
上述代码就完成加载图片的路径信息,然后依次遍历,读取图片,提取特征,提取标签信息,保存特征和标签信息,接着编码标签,然后就是划分训练集和测试集。
接着是相同的训练模型和预测的代码,和前面的分类器一样。完整版代码如下:
# 导入工具库
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from PIL import Image
from imutils import paths
import numpy as np
import argparse
import os
# 抽取图像特征
def extract_color_stats(image):
'''
将图片分成 RGB 三通道,然后分别计算每个通道的均值和标准差,然后返回
:param image:
:return:
'''
(R, G, B) = image.split()
features = [np.mean(R), np.mean(G), np.mean(B), np.std(R), np.std(G), np.std(B)]
return features
# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", type=str, default="3scenes",
help="path to directory containing the '3scenes' dataset")
ap.add_argument("-m", "--model", type=str, default="knn",
help="type of python machine learning model to use")
args = vars(ap.parse_args())
# 定义一个保存模型的字典,根据 key 来选择加载哪个模型
models = {
"knn": KNeighborsClassifier(n_neighbors=1),
"naive_bayes": GaussianNB(),
"logit": LogisticRegression(solver="lbfgs", multi_class="auto"),
"svm": SVC(kernel="rbf", gamma="auto"),
"decision_tree": DecisionTreeClassifier(),
"random_forest": RandomForestClassifier(n_estimators=100),
"mlp": MLPClassifier()
}
# 加载数据并提取特征
print("抽取图像特征中...")
imagePaths = paths.list_images(args['dataset'])
data = []
labels = []
# 循环遍历所有的图片数据
for imagePath in imagePaths:
# 加载图片,然后计算图片的颜色通道统计信息
image = Image.open(imagePath)
features = extract_color_stats(image)
data.append(features)
# 保存图片的标签信息
label = imagePath.split(os.path.sep)[-2]
labels.append(label)
# 对标签进行编码,从字符串变为整数类型
le = LabelEncoder()
labels = le.fit_transform(labels)
# 进行训练集和测试集的划分,80%数据作为训练集,其余 20%作为测试集
trainX, testX, trainY, testY = train_test_split(data, labels, random_state=3, test_size=0.2)
# print('trainX numbers={}, testX numbers={}'.format(len(trainX), len(testX)))
# 训练模型
print("[应用 '{}' 模型建模".format(args["model"]))
model = models[args["model"]]
model.fit(trainX, trainY)
# 预测并输出分类结果报告
print("模型评估")
predictions = model.predict(testX)
print(classification_report(testY, predictions, target_names=le.classes_))
完成这两份代码后,我们就可以开始运行下代码,对比不同算法在两个数据集上的性能。
4.3 不同模型建模对比
(1) KNN
K-Nearest Neighbors 分类器最简单的分类算法之一。该算法依赖于特征向量之间的距离。简单地说,KNN 算法通过在 k 个最接近的样本中最多的类别来对未知数据点进行分类。关于 KNN 的详细讲解可以阅读ShowMeAI的文章 图解机器学习 | KNN 算法及其应用。

这里我们先运行下image_classifier.py,调用默认的模型knn,看下KNN在iris数据集上的实验结果,如下所示:
$ !python iris_classifier.py --model knn
加载数据中...
应用 'knn' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 0.90 0.90 0.90 10
virginica 0.90 0.90 0.90 10
accuracy 0.93 30
macro avg 0.93 0.93 0.93 30
weighted avg 0.93 0.93 0.93 30
其中主要是给出了对每个类别的精确率、召回率、F1 以及该类别测试集数量,即分别对应 precision、recall、f1-score、support。根据最后一行第一列,可以看到 KNN 取得 93%的准确率。
接着是在三场景图片数据集上的实验结果:
$ !python image_classifier.py --model knn
抽取图像特征中...
应用 'knn' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.84 0.68 0.75 105
forest 0.78 0.77 0.77 78
highway 0.56 0.78 0.65 54
micro avg 0.73 0.73 0.73 237
macro avg 0.72 0.74 0.72 237
weighted avg 0.75 0.73 0.73 237
这里 KNN 取得 75%的准确率。
ps:实际上,运行这个算法,不同次数会有不同的结果,其主要原因是因为在划分训练集和测试集的时候,代码没有设置参数random_state,这导致每次运行划分的训练集和测试集的图片都是不同的,所以运行结果也会不相同!
(2) 朴素贝叶斯
接着是朴素贝叶斯算法,关于朴素贝叶斯算法的详细讲解可以阅读ShowMeAI的文章 图解机器学习 | 朴素贝叶斯算法详解。

分别测试两个数据集,结果如下:
$ !python iris_classifier.py --model naive_bayes
加载数据中...
应用 'naive_bayes' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 1.00 0.92 0.96 12
virginica 0.92 1.00 0.96 11
micro avg 0.97 0.97 0.97 38
macro avg 0.97 0.97 0.97 38
weighted avg 0.98 0.97 0.97 38
$ !python image_classifier.py --model naive_bayes
抽取图像特征中...
应用 'naive_bayes' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.69 0.40 0.50 88
forest 0.68 0.82 0.74 84
highway 0.61 0.78 0.68 65
micro avg 0.65 0.65 0.65 237
macro avg 0.66 0.67 0.64 237
weighted avg 0.66 0.65 0.64 237
同样,朴素贝叶斯在 iris 上有 98%的准确率,但是在图像数据集上仅有 66%的准确率。
那么,我们是否可以说明 KNN 算法比朴素贝叶斯好呢?当然是不可以的,上述结果只能说明在三场景图像数据集上,KNN 算法优于朴素贝叶斯算法。
实际上,每种算法都有各自的优缺点和适用场景,不能一概而论地说某种算法任何时候都优于另一种算法,这需要具体问题具体分析。
(3) 逻辑回归
接着是逻辑回归算法,关于逻辑回归算法的详细讲解可以阅读ShowMeAI的文章 图解机器学习 | 逻辑回归算法详解。

分别测试两个数据集,结果如下:
$ !python iris_classifier.py --model logit
加载数据中...
应用 'logit' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 1.00 0.92 0.96 12
virginica 0.92 1.00 0.96 11
micro avg 0.97 0.97 0.97 38
macro avg 0.97 0.97 0.97 38
weighted avg 0.98 0.97 0.97 38
$ !python image_classifier.py --model logit
抽取图像特征中...
应用 'logit' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.67 0.67 0.67 92
forest 0.79 0.82 0.80 82
highway 0.61 0.57 0.59 63
micro avg 0.70 0.70 0.70 237
macro avg 0.69 0.69 0.69 237
weighted avg 0.69 0.70 0.69 237
同样,逻辑回归在 iris 上有 98% 的准确率,但是在图像数据集上仅有 69% 的准确率
(4) 支持向量机 SVM
接着是 SVM 算法,关于 SVM 算法的详细讲解可以阅读ShowMeAI的文章 图解机器学习 | 支持向量机模型详解。

分别测试两个数据集,结果如下:
$ !python iris_classifier.py --model svm
加载数据中...
应用 'svm' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 1.00 0.92 0.96 12
virginica 0.92 1.00 0.96 11
micro avg 0.97 0.97 0.97 38
macro avg 0.97 0.97 0.97 38
weighted avg 0.98 0.97 0.97 38
$ !python image_classifier.py --model svm
抽取图像特征中...
应用 'svm' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.84 0.76 0.80 92
forest 0.86 0.93 0.89 84
highway 0.78 0.80 0.79 61
micro avg 0.83 0.83 0.83 237
macro avg 0.83 0.83 0.83 237
同样,SVM 在iris上有 98%的准确率,但是在图像数据集上仅有 83%的准确率。
(5) 决策树
接着是决策树算法,关于决策树算法的详细讲解可以阅读ShowMeAI的文章 图解机器学习 | 决策树模型详解。

分别测试两个数据集,结果如下:
$ !python iris_classifier.py --model decision_tree
加载数据中...
应用 'decision_tree' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 0.92 0.92 0.92 12
virginica 0.91 0.91 0.91 11
micro avg 0.95 0.95 0.95 38
macro avg 0.94 0.94 0.94 38
weighted avg 0.95 0.95 0.95 38
$ !python image_classifier.py --model decision_tree
抽取图像特征中...
应用 'decision_tree' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.71 0.74 0.72 85
forest 0.76 0.80 0.78 83
highway 0.77 0.68 0.72 69
micro avg 0.74 0.74 0.74 237
macro avg 0.75 0.74 0.74 237
weighted avg 0.74 0.74 0.74 237
同样,决策树在iris上有 98%的准确率,但是在图像数据集上仅有 74%的准确率。
(6) 随机森林
接着是随机森林算法,关于随机森林算法的详细讲解可以阅读ShowMeAI的文章 图解机器学习 | 随机森林分类模型详解。

分别测试两个数据集,结果如下:
$ !python iris_classifier.py --model random_forest
加载数据中...
应用 'random_forest' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 1.00 0.83 0.91 12
virginica 0.85 1.00 0.92 11
micro avg 0.95 0.95 0.95 38
macro avg 0.95 0.94 0.94 38
weighted avg 0.96 0.95 0.95 38
$ !python image_classifier.py --model random_forest
加载数据中...
应用 'random_forest' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.80 0.83 0.81 84
forest 0.92 0.84 0.88 90
highway 0.77 0.81 0.79 63
micro avg 0.83 0.83 0.83 237
macro avg 0.83 0.83 0.83 237
weighted avg 0.84 0.83 0.83 237
同样,随机森林在iris上有 96%的准确率,但是在图像数据集上仅有 84%的准确率。
注意了,一般如果决策树算法的效果还不错的话,随机森林算法应该也会取得不错甚至更好的结果,这是因为随机森林实际上就是多棵决策树通过集成学习方法组合在一起进行分类预测。
(7) 多层感知机

最后是多层感知机算法,分别测试两个数据集,结果如下:
$ !python iris_classifier.py --model mlp
加载数据中...
应用 'mlp' 模型建模...
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 1.00 0.92 0.96 12
virginica 0.92 1.00 0.96 11
micro avg 0.97 0.97 0.97 38
macro avg 0.97 0.97 0.97 38
weighted avg 0.98 0.97 0.97 38
$ !python image_classifier.py --model mlp
抽取图像特征中...
应用 'mlp' 模型建模...
评估模型效果...
precision recall f1-score support
coast 0.72 0.91 0.80 86
forest 0.92 0.89 0.90 79
highway 0.79 0.58 0.67 72
micro avg 0.80 0.80 0.80 237
macro avg 0.81 0.79 0.79 237
weighted avg 0.81 0.80 0.80 237
同样,多层感知机在 iris 上有 98% 的准确率,但是在图像数据集上仅有 81% 的准确率.
(8) 神经网络

最后是实现深度学习的算法,也就是nn_iris.py和basic_cnn.py这两份代码。
首先是nn_iris.py的实现,同样首先是导入库和数据的处理:
# 导入工具库
from keras.models import Sequential
from keras.layers.core import Dense
from keras.optimizers import SGD
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import load_iris
# 载入 Iris 数据集,然后进行训练集和测试集的划分,80%数据作为训练集,其余 20%作为测试集
print("加载数据中...")
dataset = load_iris()
(trainX, testX, trainY, testY) = train_test_split(dataset.data,
dataset.target, test_size=0.2)
# 将标签进行独热向量编码
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)
我们采用Keras来实现神经网络,然后这里需要将标签进行one-hot编码,即独热向量编码。
接着就是搭建网络模型的结构和训练、预测代码:
# 利用 Keras 定义网络模型
model = Sequential()
model.add(Dense(3, input_shape=(4,), activation="sigmoid"))
model.add(Dense(3, activation="sigmoid"))
model.add(Dense(3, activation="softmax"))
# 采用梯度下降训练模型
print('训练网络中...')
opt = SGD(lr=0.1, momentum=0.9, decay=0.1 / 250)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=["accuracy"])
H = model.fit(trainX, trainY, validation_data=(testX, testY), epochs=250, batch_size=16)
# 预测
print('评估模型效果')
predictions = model.predict(testX, batch_size=16)
print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1), target_names=dataset.target_names))
上述代码构建了 3 层全连接层的神经网络,前两层采用Sigmoid激活函数,然后最后一层是输出层,所以采用softmax将输出变成概率值。优化算法选择的随机梯度下降SGD,损失函数是categorical_crossentropy,迭代次数是 250 次,每一批次的数据量batch_size是 16。
完整版代码如下:
# 加载工具库
from keras.models import Sequential
from keras.layers.core import Dense
from keras.optimizers import SGD
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import load_iris
# 载入 Iris 数据集,然后进行训练集和测试集的划分,80%数据作为训练集,其余 20%作为测试集
print("加载数据中...")
dataset = load_iris()
(trainX, testX, trainY, testY) = train_test_split(dataset.data,
dataset.target, test_size=0.2)
# 将标签进行独热向量编码
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)
# 利用 Keras 定义网络模型
model = Sequential()
model.add(Dense(3, input_shape=(4,), activation="sigmoid"))
model.add(Dense(3, activation="sigmoid"))
model.add(Dense(3, activation="softmax"))
# 采用梯度下降训练模型
print('训练网络中...')
opt = SGD(lr=0.1, momentum=0.9, decay=0.1 / 250)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=["accuracy"])
H = model.fit(trainX, trainY, validation_data=(testX, testY), epochs=250, batch_size=16)
# 预测
print('评估模型效果...')
predictions = model.predict(testX, batch_size=16)
print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1), target_names=dataset.target_names))
直接运行命令python nn_iris.py,输出的结果如下:
$ python nn_iris.py
Using TensorFlow backend.
加载数据中...
训练网络中...
Train on 112 samples, validate on 38 samples
Epoch 1/250
2022-02-08 10:28:19.104933: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 AVX512F FMA
112/112 [==============================] - 0s 2ms/step - loss: 1.1454 - acc: 0.3214 - val_loss: 1.1867 - val_acc: 0.2368
Epoch 2/250
112/112 [==============================] - 0s 48us/step - loss: 1.0828 - acc: 0.3929 - val_loss: 1.2132 - val_acc: 0.5000
Epoch 3/250
112/112 [==============================] - 0s 47us/step - loss: 1.0491 - acc: 0.5268 - val_loss: 1.0593 - val_acc: 0.4737
...
Epoch 248/250
112/112 [==============================] - 0s 46us/step - loss: 0.1319 - acc: 0.9554 - val_loss: 0.0407 - val_acc: 1.0000
Epoch 249/250
112/112 [==============================] - 0s 46us/step - loss: 0.1024 - acc: 0.9643 - val_loss: 0.1595 - val_acc: 0.8947
Epoch 250/250
112/112 [==============================] - 0s 47us/step - loss: 0.0795 - acc: 0.9821 - val_loss: 0.0335 - val_acc: 1.0000
评估模型效果...
precision recall f1-score support
setosa 1.00 1.00 1.00 9
versicolor 1.00 1.00 1.00 10
virginica 1.00 1.00 1.00 19
avg / total 1.00 1.00 1.00 38
这里得到的是 100%的准确率。
(9) CNN

最后我们要应用卷积神经网络,我们实现一下basic_cnn.py代码。
同样首先是导入必须的库函数:
# 导入工具库
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras.optimizers import Adam
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from PIL import Image
from imutils import paths
import numpy as np
import argparse
import os
# 配置参数
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", type=str, default="3scenes",
help="path to directory containing the '3scenes' dataset")
args = vars(ap.parse_args())
同样是要导入Keras来建立CNN的网络模型,另外因为是处理图像数据,所以PIL、imutils也是要导入的。
然后是加载数据和划分训练集和测试集,对于加载数据,这里直接采用原始图像像素数据,只需要对图像数据做统一尺寸的调整,这里是统一调整为 32×32,并做归一化到[0,1]的范围。
# 加载数据并提取特征
print("抽取图像特征中...")
imagePaths = paths.list_images(args['dataset'])
data = []
labels = []
# 循环遍历所有的图片数据
for imagePath in imagePaths:
# 加载图片,然后调整成 32×32 大小,并做归一化到 [0,1]
image = Image.open(imagePath)
image = np.array(image.resize((32, 32))) / 255.0
data.append(image)
# 保存图片的标签信息
label = imagePath.split(os.path.sep)[-2]
labels.append(label)
# 对标签编码,从字符串变为整型
lb = LabelBinarizer()
labels = lb.fit_transform(labels)
# 划分训练集和测试集
(trainX, testX, trainY, testY) = train_test_split(np.array(data), np.array(labels), test_size=0.25)
接着定义了一个 4 层的CNN网络结构,包含 3 层卷积层和最后一层输出层,优化算法采用的是 Adam 而不是SGD。代码如下所示:
# 定义 CNN 网络模型结构
model = Sequential()
model.add(Conv2D(8, (3, 3), padding="same", input_shape=(32, 32, 3)))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Conv2D(16, (3, 3), padding="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Conv2D(32, (3, 3), padding="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(3))
model.add(Activation("softmax"))
# 训练模型
print("训练网络中...")
opt = Adam(lr=1e-3, decay=1e-3 / 50)
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
H = model.fit(trainX, trainY, validation_data=(testX, testY),
epochs=50, batch_size=32)
# 预测
print("评估模型效果...")
predictions = model.predict(testX, batch_size=32)
print(classification_report(testY.argmax(axis=1),
predictions.argmax(axis=1), target_names=lb.classes_))
运行命令python basic_cnn.py,输出结果如下:
$ python basic_cnn.py
Using TensorFlow backend.
加载图像数据...
训练网络中...
Train on 711 samples, validate on 237 samples
Epoch 1/50
711/711 [==============================] - 0s 629us/step - loss: 1.0647 - acc: 0.4726 - val_loss: 0.9920 - val_acc: 0.5359
Epoch 2/50
711/711 [==============================] - 0s 313us/step - loss: 0.9200 - acc: 0.6188 - val_loss: 0.7778 - val_acc: 0.6624
Epoch 3/50
711/711 [==============================] - 0s 308us/step - loss: 0.6775 - acc: 0.7229 - val_loss: 0.5310 - val_acc: 0.7553
...
Epoch 48/50
711/711 [==============================] - 0s 307us/step - loss: 0.0627 - acc: 0.9887 - val_loss: 0.2426 - val_acc: 0.9283
Epoch 49/50
711/711 [==============================] - 0s 310us/step - loss: 0.0608 - acc: 0.9873 - val_loss: 0.2236 - val_acc: 0.9325
Epoch 50/50
711/711 [==============================] - 0s 307us/step - loss: 0.0587 - acc: 0.9887 - val_loss: 0.2525 - val_acc: 0.9114
评估模型效果...
precision recall f1-score support
coast 0.85 0.96 0.90 85
forest 0.99 0.94 0.97 88
highway 0.91 0.80 0.85 64
avg / total 0.92 0.91 0.91 237
CNN的准确率是达到 92%,它是优于之前的几种机器学习算法的结果。
5.小结
这篇简单的机器学习教程文章中,我们调用现有的库来应用对应的机器学习算法,解决了 2 个简单的场景问题。通过这份简单的入门教程,希望大家了解到:
(1) 没有任何一种算法是完美的,可以完全适用所有的场景,即便是目前很热门的深度学习方法,也存在它的局限性,所以应该具体问题具体分析!
(2) 经典的 5 步机器学习操作流程:
- 问题抽象与理解
- 数据准备与处理(预处理、特征提取、特征工程等)
- 各种机器学习算法
- 实验结果分析与对比
- 模型选择与调优
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | SKLearn 入门与简单应用案例

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/202
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
在前面的机器学习案例中,我们使用了 Python 机器学习工具库 Scikit-Learn,它建立在 NumPy、SciPy、Pandas 和 Matplotlib 之上,也是最常用的 Python 机器学习工具库之一,里面的 API 的设计非常好,所有对象的接口简单,很适合新手上路。ShowMeAI在本篇内容中对 Scikit-Learn 做一个介绍。

1.SKLearn 是什么
Scikit-Learn 也简称 SKLearn,是一个基于 Python 语言的机器学习工具,它对常用的机器学习方法进行了封装,例如,分类、回归、聚类、降维、模型评估、数据预处理等,我们只需调用对应的接口即可。


在 SKLearn 的官网上,写着以下四点介绍:
- 一个简单高效的数据挖掘和数据分析工具。
- 构建在 NumPy,SciPy 和 matplotlib 上。
- 可供大家在各种环境中重复使用。
- 开源,可商业使用–BSD 许可证。
SKLearn 官网:scikit-learn.org/stable/
SKLearn 的快速使用方法也推荐大家查看ShowMeAI的文章和速查手册 AI 建模工具速查|Scikit-learn 使用指南
2.安装 SKLearn
安装 SKLearn 非常简单,命令行窗口中输入命令:
pip install scikit-learn
我们也可以使用清华镜像源安装,通常速度会更快一些:
pip install scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple
3.SKLearn 常用接口
对于机器学习整个流程中涉及到的常用操作,SKLearn 中几乎都有现成的接口可以直接调用,而且不管使用什么处理器或者模型,它的接口一致度都非常高。
3.1 数据集导入
更多数据集请参考 SKLearn 官网:scikit-learn.org/stable/modules/classes.html?highlight=dataset#module-sklearn.datasets

#鸢尾花数据集
from sklearn.datasets import load_iris
#乳腺癌数据集
from sklearn.datasets import load_breast_cancer
#波士顿房价数据集
from sklearn.datasets import load_boston
3.2 数据预处理
官网链接:scikit-learn.org/stable/modules/classes.html#module-sklearn.preprocessing

#拆分数据集
from sklearn.model_selection import train_test_split
#数据缩放
from sklearn.preprocessing import MinMaxScaler
3.3 特征抽取
官网链接:scikit-learn.org/stable/modules/classes.html#module-sklearn.feature_extraction

from sklearn.feature_extraction import DictVectorizer
v = DictVectorizer(sparse=False)
D = [{'foo': 1, 'bar': 2}, {'foo': 3, 'baz': 1}]
X = v.fit_transform(D)
3.4 特征选择
官网链接:scikit-learn.org/stable/modules/classes.html#module-sklearn.feature_selection

from sklearn.datasets import load_digits
from sklearn.feature_selection import SelectKBest, chi2
X, y = load_digits(return_X_y=True)
# 特征选择
X_new = SelectKBest(chi2, k=20).fit_transform(X, y)
3.5 常用模型
官网链接:scikit-learn.org/stable/modules/classes.html

#KNN 模型
from sklearn.neighbors import KNeighborsClassifier
#决策树
from sklearn.tree import DecisionTreeClassifier
#支持向量机
from sklearn.svm import SVC
#随机森林
from sklearn.ensemble import RandomForestClassifier
3.6 建模拟合与预测

#拟合训练集
knn.fit(X_train,y_train)
#预测
y_pred=knn.predict(X_test)
3.7 模型评估
官网链接:scikit-learn.org/stable/modules/classes.html#sklearn-metrics-metrics

#求精度
knn.score(X_test,y_test)
#绘制混淆矩阵
from sklearn.metrics import confusion_matrix
#绘制 ROC 曲线
from sklearn.metrics import roc_curve,roc_auc_score
3.8 典型的建模流程示例
典型的一个机器学习建模应用流程遵循【数据准备】【数据预处理】【特征工程】【建模与评估】【模型优化】这样的一些流程环节。
# 加载数据
import numpy as np
import urllib
# 下载数据集
url = "http://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
raw_data = urllib.urlopen(url)
# 加载 CSV 文件
dataset = np.loadtxt(raw_data, delimiter=",")
# 区分特征和标签
X = dataset[:,0:7]
y = dataset[:,8]
# 数据归一化
from sklearn import preprocessing
# 幅度缩放
scaled_X = preprocessing.scale(X)
# 归一化
normalized_X = preprocessing.normalize(X)
# 标准化
standardized_X = preprocessing.scale(X)
# 特征选择
from sklearn import metrics
from sklearn.ensemble import ExtraTreesClassifier
model = ExtraTreesClassifier()
model.fit(X, y)
# 特征重要度
print(model.feature_importances_)
# 建模与评估
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(X, y)
print('MODEL')
print(model)
# 预测
expected = y
predicted = model.predict(X)
# 输出评估结果
print('RESULT')
print(metrics.classification_report(expected, predicted))
print('CONFUSION MATRIX')
print(metrics.confusion_matrix(expected, predicted))
# 超参数调优
from sklearn.model_selection import GridSearchCV
param_grid = {'penalty' : ['l1', 'l2', 'elasticnet'],
'C': [0.1, 1, 10]}
grid_search = GridSearchCV(LogisticRegression(), param_grid, cv=5)
参考资料
- 图解机器学习算法 | 从入门到精通系列
- SKLearn 官网:
scikit-learn.org/stable/
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | SKLearn 最全应用指南

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/203
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
我们在上一篇SKLearn 入门与简单应用案例里给大家讲到了 SKLearn 工具的基本板块与使用方法,在本篇内容中,我们展开讲解 SKLearn 的进阶与核心内容。SKLearn 中有六大任务模块,如下图所示:分别是分类、回归、聚类、降维、模型选择和预处理。
- SKLearn 官网:https://scikit-learn.org/stable/
- SKLearn 的快速使用方法也推荐大家查看ShowMeAI的文章和速查手册 AI 建模工具速查|Scikit-learn 使用指南


在 SKLearn 中,因为做了上层的封装,分类模型、回归模型、聚类与降维模型、预处理器等等都叫做估计器(estimator),就像在 Python 里「万物皆对象」,在 SKLearn 里「万物皆估计器」。
在本篇内容中,我们将给大家进一步深入讲解 scikit-learn 工具库的使用方法,力求完整覆盖 sklearn 工具库应用的方方面面。本文的内容板块包括:
- ① 机器学习基础知识:机器学习定义与四要素:数据、任务、性能度量和模型。机器学习概念,以便和 SKLearn 对应匹配上。
- ② SKLearn 讲解:API 设计原理,sklearn 几大特点:一致性、可检验、标准类、可组合和默认值,以及 SKLearn 自带数据以及储存格式。
- ③ SKLearn 三大核心 API 讲解:包括估计器、预测器和转换器。这个板块很重要,大家实际应用时主要是借助于核心 API 落地。
- ④ SKLearn 高级 API 讲解:包括简化代码量的流水线(Pipeline 估计器),集成模型(Ensemble 估计器)、有多类别-多标签-多输出分类模型(Multiclass 和 Multioutput 估计器)和模型选择工具(Model Selection 估计器)。
1.机器学习简介
关于本节内容,强烈推荐大家阅读ShowMeAI文章 图解机器学习 | 机器学习基础知识 和 图解机器学习 | 模型评估方法与准则 ,ShowMeAI对相关知识内容展开做了详细讲解。
1.1 定义和构成元素
何为机器学习?大师汤姆米切尔(Tom Mitchell)对机器学习定义的原话是:
A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P if its performance at tasks in T, as measured by P, improves with experience E.

这段英文中有两个词 computer program 和 learn,翻译成中文就是机器(计算机程序)和学习,整体翻译下来就是说:如果计算机程序在 T 任务上的性能(由 P 衡量)随着经验 E 而提高,则称计算机程序从经验 E 中学习某类任务 T。
由上述机器学习的定义可知机器学习包含四个元素:
- 数据(Data)
- 任务(Task)
- 性能度量(Quality Metric)
- 算法(Algorithm)

1.2 数据
数据(data)是信息的载体。数据可以有以下划分方式:

-
从「数据具体类型」维度划分:结构化数据和非结构化数据。
- 结构化数据(structured data)是由二维表结构来逻辑表达和实现的数据。
- 非结构化数据是没有预定义的数据,不便用数据库二维表来表现的数据。非结构化数据包括图片,文字,语音和视频等。
-
从「数据表达形式」维度划分:原始数据和加工数据。
-
从「数据统计性质」维度划分:样本内数据和样本外数据。
对于非结构数据,通常神经网络有更好的效果,可以参考ShowMeAI的文章 Python 机器学习算法实践中的图像建模例子。
机器学习模型很多时候使用的是结构化数据,即二维的数据表。我们这里以 iris 花瓣数据集举例,如下图。

下面术语大家在深入了解机器学习前一定要弄清楚:
- 每行的记录(这是一朵鸢尾花的数据统计),称为一个「样本(sample)」。
- 反映样本在某方面的性质,例如萼片长度(Sepal Length)、花瓣长度(Petal Length),称为「特征(feature)」。
- 特征上的取值,例如「样本 1」对应的 5.1、3.5 称为「特征值(feature value)」。
- 关于样本结果的信息,例如 Setosa、Versicolor,称为「类别标签(class label)」。
- 包含标签信息的示例,则称为「样例(instance)」,即
样例=(特征,标签)。 - 从数据中学得模型的过程称为「学习(learning)」或「训练(training)」。
- 在训练数据中,每个样例称为「训练样例(training instance)」,整个集合称为「训练集(training set)」。
1.3 任务
根据学习的任务模式(训练数据是否有标签),机器学习可分为几大类:
- 监督学习(有标签)
- 无监督学习(无标签)
- 半监督学习(有部分标签)
- 强化学习(有延迟的标签)
下图画出机器学习各类之间的关系。

1.4 性能度量
回归和分类任务中最常见的误差函数以及一些有用的性能度量如下,详细内容可以参考ShowMeAI文章 机器学习评估与度量准则。


2. SKLearn 数据
SKLearn 作为通用机器学习建模的工具包,包含六个任务模块和一个数据导入模块:
首先看看 SKLearn 默认数据格式和自带数据集。
2.1 SKLearn 默认数据格式
Sklean 里模型能直接使用的数据有两种形式:
- Numpy 二维数组(ndarray)的稠密数据(dense data),通常都是这种格式。
- SciPy 矩阵(scipy.sparse.matrix)的稀疏数据(sparse data),比如文本分析每个单词(字典有 100000 个词)做独热编码得到矩阵有很多 0,这时用 ndarray 就不合适了,太耗内存。
2.2 自带数据集
SKLearn 里面有很多自带数据集供用户使用。
比如在之前文章Python 机器学习算法实践中用到的鸢尾花数据集,包含四个特征(萼片长/宽和花瓣长/宽)和三个类别。

我们可以直接从 SKLearn 里面的 datasets 模块中引入,代码如下(代码可以在 线上 Jupyter 环境 中运行):
# 导入工具库
from sklearn.datasets import load_iris
iris = load_iris()
#数据是以「字典」格式存储的,看看 iris 的键有哪些。
iris.keys()
输出如下:
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
读取数据集的信息:
#输出 iris 数据中特征的大小、名称等信息和前五个样本。
n_samples, n_features = iris.data.shape
print((n_samples, n_features))
print(iris.feature_names)
print(iris.target.shape)
print(iris.target_names)
iris.data[0:5]
输出如下:
(150, 4)
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
(150,)
['setosa' 'versicolor' 'virginica']
array([[5.1, 3.5, 1.4, 0.2],
[4.9, 3\. , 1.4, 0.2],
[4.7, 3.2, 1.3, 0.2],
[4.6, 3.1, 1.5, 0.2],
[5\. , 3.6, 1.4, 0.2]])
构建 Dataframe 格式的数据集:
# 将 X 和 y 合并为 Dataframe 格式数据
import pandas as pd
import seaborn as sns
iris_data = pd.DataFrame( iris.data,
columns=iris.feature_names )
iris_data['species'] = iris.target_names[iris.target]
iris_data.head(3).append(iris_data.tail(3))
输出如下:
| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | species | |
|---|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa |
| 147 | 6.5 | 3.0 | 5.2 | 2.0 | virginica |
| 148 | 6.2 | 3.4 | 5.4 | 2.3 | virginica |
| 149 | 5.9 | 3.0 | 5.1 | 1.8 | virginica |
我们使用 seaborn 来做一些数据分析,查看一下数据的分布特性。这里使用到的是成对维度的关联分析,关于 seaborn 的使用方法可以参阅ShowMeAI的文章 seaborn 工具与数据可视化教程。
# 使用 Seaborn 的 pairplot 查看两两特征之间的关系
sns.pairplot( iris_data, hue='species', palette='husl' )

2.3 数据集引入方式
前面提到的是鸢尾花 iris 数据集,我们通过 load_iris 加载进来,实际上 SKLearn 有三种引入数据形式。
- 打包好的数据:对于小数据集,用
sklearn.datasets.load_* - 分流下载数据:对于大数据集,用
sklearn.datasets.fetch_* - 随机创建数据:为了快速展示,用
sklearn.datasets.make_*
上面这个星号*指代具体文件名,如果大家在 Jupyter 这种 IDE 环境中,可以通过 tab 制表符自动补全和选择。
- datasets.load_
- datasets.fetch_
- datasets.make_
比如我们调用load_iris
from sklearn import datasets
datasets.load_iris
输出如下:
<function sklearn.datasets.base.load_iris(return_X_y=False)>
我们调用load_digits加载手写数字图像数据集
digits = datasets.load_digits()
digits.keys()
输出:
dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])
我们再来看看通过 fetch 拉取数据的示例:
#加州房屋数据集
california_housing = datasets.fetch_california_housing()
california_housing.keys()
输出:
dict_keys(['data', 'target', 'feature_names', 'DESCR'])
3.SKLearn 核心 API
我们前面提到 SKLearn 里万物皆估计器。估计器是个非常抽象的叫法,不严谨的一个理解,我们可以视其为一个模型(用来回归、分类、聚类、降维),或一套流程(预处理、网格搜索交叉验证)。
本节三大 API 其实都是估计器:
- 估计器(estimator)通常是用于拟合功能的估计器。
- 预测器(predictor)是具有预测功能的估计器。
- 转换器(transformer)是具有转换功能的估计器。

3.1 估计器
任何可以基于数据集对一些参数进行估计的对象都被称为估计器,它有两个核心点:
- ① 需要输入数据。
- ② 可以估计参数。

估计器首先被创建,然后被拟合。
-
创建估计器:需要设置一组超参数,比如
- 线性回归里超参数
normalize=True - K 均值里超参数
n_clusters=5
- 线性回归里超参数
-
拟合估计器:需要训练集
- 在监督学习中的代码范式为
model.fit(X_train, y_train) - 在无监督学习中的代码范式为
model.fit(X_train)
- 在监督学习中的代码范式为
拟合之后可以访问 model 里学到的参数,比如线性回归里的特征系数 coef,或 K 均值里聚类标签 labels,如下(具体的可以在 SKLearn 文档的每个模型页查到)。
model.coef_model.labels_
下面看看监督学习的「线性回归」和无监督学习的「K 均值聚类」的具体例子。
(1) 线性回归

首先从 SKLearn 工具库的linear_model中引入LinearRegression;创建模型对象命名为 model,设置超参数normalize为True(在每个特征值上做标准化,这样能保证拟合的稳定性,加速模型拟合速度)。
from sklearn.linear_model import LinearRegression
model = LinearRegression(normalize=True)
model
输出:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=True)
创建完后的估计器会显示所有的超参数(比如刚才设置的normalize=True),未设置的超参数都使用默认值。
自己创建一个简单数据集(一条直线上的数据点),简单讲解一下估计器里面的特征。
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = 2 * x + 1
plt.plot( x, y, 'o' )

在我们生成的数据里,X 是一维,我们做一点小小的调整,用np.newaxis加一个维度,把[1,2,3]转成[[1],[2],[3]],这样的数据形态可以符合 sklearn 的要求。接着把 X 和 y 送入fit()函数来拟合线性模型的参数。
X = x[:, np.newaxis]
model.fit( X, y )
输出为:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=True)
拟合完后的估计器和创建完似乎没有差别,但我们已经可以用model.param_访问到拟合完数据的参数了,如下代码。
print( model.coef_ )
print( model.intercept_ )
# 输出结果
# [2.]
# 0.9999999999999982
(2) K 均值

我们来看看聚类的例子,先从 SKLearn 的 cluster 中导入KMeans,初始化模型对象命名为 model,设置超参数n_cluster为 3(为了展示方便而我们知道用的 iris 数据集有 3 类,实际上可以设置不同数量的n_cluster)。
虽然 iris 数据里包含标签 y,但在无监督的聚类中我们不会使用到这个信息。
from sklearn.cluster import KMeans
model = KMeans( n_clusters=3 )
model
输出为:
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=None, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
iris 数据集包含四维特征(萼片长、萼片宽、花瓣长、花瓣宽),在下面的例子中我们希望可视化,这里我们简单选取两个特征(萼片长、萼片宽)来做聚类并且可视化结果。
注意下面代码
X = iris.data[:,0:2]其实就是提取特征维度。
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data[:,0:2]
model.fit(X)
输出为:
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=None, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
拟合完后的估计器和创建完似乎没有差别,但我们已经可以用model.param_访问到拟合完数据的参数了,如下代码。
print( model.cluster_centers_, '\n')
print( model.labels_, '\n' )
print( model.inertia_, '\n')
print(iris.target)
[[5.77358491 2.69245283]
[6.81276596 3.07446809]
[5.006 3.428 ]]
[2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 0 1 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
1 1 1 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 1 0 1 1 1 1
1 1 0 0 1 1 1 1 0 1 0 1 0 1 1 0 0 1 1 1 1 1 0 0 1 1 1 0 1 1 1 0 1 1 1 0 1
1 0]
37.05070212765958
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2]
这里解释一下 KMeans 模型这几个参数:
model.clustercenters:簇中心。三个簇意味着有三个坐标。model.labels_:聚类后的标签。model.inertia_:所有点到对应的簇中心的距离平方和(越小越好)
小结
虽然上面以有监督学习的 Linear Regression 和无监督学习的 KMeans 举例,但实际上你可以将它们替换成其他别的模型,比如监督学习的 Logistic Regression 和无监督学习的 DBSCAN。它们都是「估计器」,因此都有fit()方法。
使用它们的通用伪代码如下:

# 有监督学习
from sklearn.xxx import SomeModel
# xxx 可以是 linear_model 或 ensemble 等
model = SomeModel( hyperparameter )
model.fit( X, y )
# 无监督学习
from sklearn.xxx import SomeModel
# xxx 可以是 cluster 或 decomposition 等
model = SomeModel( hyperparameter )
model.fit( X )
3.2 预测器
预测器是估计器做的一个延展,具备对数据进行预测的功能。

预测器最常见的是predict()函数:
model.predict(X_test):评估模型在新数据上的表现。model.predict(X_train):确认模型在老数据上的表现。
为了进行新数据评估,我们先将数据分成 80:20 的训练集(X_train, y_train)和测试集(X_test, y_test),再用从训练集上拟合 fit()的模型在测试集上预测predict()。
from sklearn.datasets import load_iris
iris = load_iris()
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split( iris['data'],
iris['target'],
test_size=0.2 )
print( 'The size of X_train is ', X_train.shape )
print( 'The size of y_train is ', y_train.shape )
print( 'The size of X_test is ', X_test.shape )
print( 'The size of y_test is ', y_test.shape )
The size of X_train is (120, 4)
The size of y_train is (120,)
The size of X_test is (30, 4)
The size of y_test is (30,)
predict & predict_proba
对于分类问题,我们不仅想知道预测的类别是什么,有时我们还希望获取预测概率等信息。前者用 predict(),后者用predict_proba()。
y_pred = model.predict( X_test )
p_pred = model.predict_proba( X_test )
print( y_test, '\n' )
print( y_pred, '\n' )
print( p_pred )
score & decision_function
预测器里还有额外的两个函数可以使用。在分类问题中:
score()返回的是分类准确率。decision_function()返回的是每个样例在每个类下的分数值。
print( model.score( X_test, y_test ) )
print( np.sum(y_pred==y_test)/len(y_test) )
decision_score = model.decision_function( X_test )
print( decision_score )
小结
估计器都有fit()方法,预测器都有predict()和score()方法,言外之意不是每个预测器都有predict_proba()和decision_function()方法,这个在用的时候查查官方文档就清楚了(比如RandomForestClassifier就没有decision_function()方法)。
使用它们的通用伪代码如下:

# 有监督学习
from sklearn.xxx import SomeModel
# xxx 可以是 linear_model 或 ensemble 等
model = SomeModel( hyperparameter )
model.fit( X, y )
y_pred = model.predict( X_new )
s = model.score( X_new )
# 无监督学习
from sklearn.xxx import SomeModel
# xxx 可以是 cluster 或 decomposition 等
model = SomeModel( hyperparameter )
model.fit( X )
idx_pred = model.predict( X_new )
s = model.score( X_new )
3.3 转换器
转换器是一种估计器,也有拟合功能,对比预测器做完拟合来预测,转换器做完拟合来转换。核心点如下:
- 估计器里
fit + predict - 转换器里
fit + transform

本节介绍两大类转换器:
- 将类别型变量(categorical)编码成数值型变量(numerical)
- 规范化(normalize)或标准化(standardize)数值型变量

(1) 类别型变量编码
① LabelEncoder&OrdinalEncoder
LabelEncoder 和 OrdinalEncoder 都可以将字符转成数字,但是:
- LabelEncoder 的输入是一维,比如 1d ndarray
- OrdinalEncoder 的输入是二维,比如 DataFrame
# 首先给出要编码的列表 enc 和要解码的列表 dec。
enc = ['red','blue','yellow','red']
dec = ['blue','blue','red']
# 从 sklearn 下的 preprocessing 中引入 LabelEncoder,再创建转换器起名 LE,不需要设置任何超参数。
from sklearn.preprocessing import LabelEncoder
LE = LabelEncoder()
print(LE.fit(enc))
print( LE.classes_ )
print( LE.transform(dec) )
LabelEncoder()
['blue' 'yellow' 'red']
[0 1 2]
除了 LabelEncoder,OrdinalEncoder 也可以完成编码。如下代码所示:
from sklearn.preprocessing import OrdinalEncoder
OE = OrdinalEncoder()
enc_DF = pd.DataFrame(enc)
dec_DF = pd.DataFrame(dec)
print( OE.fit(enc_DF) )
print( OE.categories_ )
print( OE.transform(dec_DF) )
OrdinalEncoder(categories='auto', dtype=<class 'numpy.float64'>)
[array(['blue', 'yellow', 'red'], dtype=object)]
[[0.]
[1.]
[2.]]
上面这种编码的问题是,在编码过后会带来不同类别的大小关系,比如这里 3 种颜色其实本质上是平等的,没有大小关系。
我们的另外一种类别型数据编码方式,独热向量编码(one-hot encoding)可以解决这个问题,大家继续往下看。
② OneHotEncoder
独热向量编码其实就是把一个整数用向量的形式表现。上图右侧就是对颜色做独热向量编码。转换器 OneHotEncoder 可以接受两种类型的输入:
- ① 用 LabelEncoder 编码好的一维数组
- ② DataFrame
一、用 LabelEncoder 编码好的一维数组(元素为整数),重塑(用 reshape(-1,1))成二维数组作为 OneHotEncoder 输入。
from sklearn.preprocessing import OneHotEncoder
OHE = OneHotEncoder()
num = LE.fit_transform( enc )
print( num )
OHE_y = OHE.fit_transform( num.reshape(-1,1) )
OHE_y
[2 0 1 2]
输出为:
<4x3 sparse matrix of type '<class 'numpy.float64'>'
with 4 stored elements in Compressed Sparse Row format>
上面结果解释如下:
- 第 3 行打印出编码结果[2 0 1 2]。
- 第 5 行将其转成独热形式,输出是一个「稀疏矩阵」形式,因为实操中通常类别很多,因此就一步到位用稀疏矩阵来节省内存。
想看该矩阵里具体内容,用toarray()函数。
OHE_y.toarray()
输出为:
array([[0., 0., 1.],
[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
二、用 DataFrame 作为 OneHotEncoder 输入。
OHE = OneHotEncoder()
OHE.fit_transform( enc_DF ).toarray()
输出为:
array([[0., 0., 1.],
[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
(2) 特征缩放
数据要做的最重要的转换之一是特征缩放(feature scaling)。类似逻辑回归,神经网络这种计算型模型,对于不同特征的幅度大小差异是敏感的。
具体来说,对于某个特征,我们有两种变换方法:
- 标准化(standardization):每个维度的特征减去该特征均值,除以该维度的标准差。
- 规范化(normalization):每个维度的特征减去该特征最小值,除以该特征的最大值与最小值之差。

① MinMaxScaler
如上图所示,MinMaxScaler 会根据特征的最大最小取值,对数据进行幅度缩放。
from sklearn.preprocessing import MinMaxScaler
X = np.array( [0, 0.5, 1, 1.5, 2, 100] )
X_scale = MinMaxScaler().fit_transform( X.reshape(-1,1) )
X_scale
输出为:
array([[0\. ],
[0.005],
[0.01 ],
[0.015],
[0.02 ],
[1\. ]])
② StandardScaler
StandardScaler 做的事情是调整数据分布,尽量接近正态分布。
from sklearn.preprocessing import StandardScaler
X_scale = StandardScaler().fit_transform( X.reshape(-1,1) )
X_scale
输出为:
array([[-0.47424487],
[-0.46069502],
[-0.44714517],
[-0.43359531],
[-0.42004546],
[ 2.23572584]])
注意:
fit()函数只能作用在训练集上,如果希望对测试集变换,只要用训练集上 fit 好的转换器去 transform 即可。不能在测试集上 fit 再 transform,否则训练集和测试集的变换规则不一致,模型学习到的信息就无效了。
4.高级 API
我们在这节中给大家介绍 SKLearn 的「高级 API」,即五大元估计器(集成功能的 Ensemble,多分类和多标签的 Multiclass,多输出的 Multioutput,选择模型的 Model Selection,流水线的 Pipeline)。

ensemble.BaggingClassifierensemble.VotingClassifiermulticlass.OneVsOneClassifiermulticlass.OneVsRestClassifiermultioutput.MultiOutputClassifiermodel_selection.GridSearchCVmodel_selection.RandomizedSearchCVpipeline.Pipeline
4.1 Ensemble 估计器

如上图:分类器统计每个子分类器的预测类别数,再用「多数投票」原则得到最终预测。回归器计算每个子回归器的预测平均值。
最常用的 Ensemble 估计器排列如下:
AdaBoostClassifier:逐步提升分类器AdaBoostRegressor:逐步提升回归器BaggingClassifier:Bagging 分类器BaggingRegressor:Bagging 回归器GradientBoostingClassifier:梯度提升分类器GradientBoostingRegressor:梯度提升回归器RandomForestClassifier:随机森林分类器RandomForestRegressor:随机森林回归器VotingClassifier:投票分类器VotingRegressor:投票回归器
我们用鸢尾花数据 iris,拿以下 estimator 来举例:
- 含同质估计器
RandomForestClassifier - 含异质估计器
VotingClassifier
首先将数据分成 80:20 的训练集和测试集,并引入 metrics 来计算各种性能指标。
from sklearn.datasets import load_iris
iris = load_iris()
from sklearn.model_selection import train_test_split
from sklearn import metrics
X_train, X_test, y_train, y_test = train_test_split(iris['data'], iris['target'], test_size=0.2)
(1) RandomForestClassifier

随机森林RandomForestClassifier 通过控制n_estimators超参数来决定基估计器的个数,在这里是 4 棵决策树(森林由树组成);此外每棵树的最大树深为 5(max_depth=5)。
from sklearn.ensemble import RandomForestClassifier
RF = RandomForestClassifier( n_estimators=4, max_depth=5 )
RF.fit( X_train, y_train )
输出为:
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=5, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=4,
n_jobs=None, oob_score=False, random_state=None,
verbose=0, warm_start=False)
元估计器和预估器一样也有fit()。下面看看随机森林里包含的估计器个数和其本身。
print( RF.n_estimators )
RF.estimators_
输出为:
4
[DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=705712365, splitter='best'),
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=1026568399, splitter='best'),
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=1987322366, splitter='best'),
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=1210538094, splitter='best')]
拟合 RF 完再做预测,用 metrics 里面的 accuracy_score 来计算准确率。训练准确率 98.33%,测试准确率 100%。
print ( "RF - Accuracy (Train): %.4g" %
metrics.accuracy_score(y_train, RF.predict(X_train)) )
print ( "RF - Accuracy (Test): %.4g" %
metrics.accuracy_score(y_test, RF.predict(X_test)) )
RF - Accuracy (Train): 1
RF - Accuracy (Test): 0.9667
(2) VotingClassifier

和随机森林由同质分类器「决策树」不同,投票分类器由若干个异质分类器组成。下面我们用 VotingClassifier 建立个含有逻辑回归(Logistic regression)、随机森林(RandomForest)和高斯朴素贝叶斯(GNB)三个分类器的集成模型。
RandomForestClassifier 的基分类器只能是决策树,因此只用通过控制n_estimators超参数来决定树的个数,而 VotingClassifier 的基分类器要输入每个异质分类器。
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
LR = LogisticRegression( solver='lbfgs', multi_class='multinomial' )
RF = RandomForestClassifier( n_estimators=5 )
GNB = GaussianNB()
Ensemble = VotingClassifier( estimators=[('lr', LR), (‘rf', RF), ('gnb', GNB)], voting='hard' )
Ensemble. fit( X_train, y_train )
结果如下:
VotingClassifier(estimators=[('lr', LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,intercept_scaling=1, max_iter=100, multi_class='multinomial',n_jobs=None, penalty='12', random_state=None, solver='lbfgs',tol=0.0001, verbose=6, warm_start=False)), ('rf', ...e, verbose=0,warm_start=False)), ('gnb', GaussianNB(priors=None, var_smoothing=1e-09))],flatten_transform=None, n_jobs=None, voting='hard', weights=None)
看看 Ensemble 集成模型里包含的估计器个数和其本身。
print( len(Ensemble.estimators_) )
Ensemble.estimators_
结果如下:
3
[LogisticRegression(C=1.0, class_weight-None, dual-False, fit_intercept=True,intercept_scaling=1, max_iter=100, multi_class='multinomial',n_jobs-None, penalty="12", random_state-None, solver='1bfgs',t01=0.0001, verbose=0, warm_start=False),
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',max_depth=None, max_features='auto', max_leaf_nodes=None,min_impurity_decrease-0.0, min_impurity_splitmin_samples_leaf=1, min_samples_split=2,min_weight_fraction_leaf=0.0, n_estimator:oob_score=False, random_state-None, verbose=
warm_start=False),
GaussianNB(priors-None, var_smoothing=1e-9)]
对比元估计器和它三个组成元素的表现,下过表现如下:
# 拟合
LR.fit( X_train, y_train )
RF.fit( X_train, y_train )
GNB.fit( X_train, y_train )
# 评估效果
print ( "LR - Accuracy (Train): %.4g" % metrics.accuracy_score(y_train, LR.predict(X_train)) )
print ( "RF - Accuracy (Train): %.4g" % metrics.accuracy_score(y_train, RF.predict(X_train)) )
print ( "GNB - Accuracy (Train): %.4g" % metrics.accuracy_score(y_train, GNB.predict(X_train)) )
print ( "Ensemble - Accuracy (Train): %.4g" % metrics.accuracy_score(y_train, Ensemble.predict(X_train)) )
print ( "LR - Accuracy (Test): %.4g" % metrics.accuracy_score(y_test, LR.predict(X_test)) )
print ( "RF - Accuracy (Test): %.4g" % metrics.accuracy_score(y_test, RF.predict(x_test)) )
print ( "GNB - Accuracy (Test): %.4g" % metrics.accuracy_score(y_test, RF.predict(X_test)) )
print ( "Ensemble - Accuracy (Test): %.4g" % metrics.accuracy_score(y test, Ensemble.predict(X_test)) )
# 运行结果
LR - Accuracy (Train): 0.975
RF - Accuracy (Train): 0.9833
GNB - Accuracy (Train): 0.95
Ensemble - Accuracy (Train): 0.9833
LR - Accuracy (Test): 1
RF - Accuracy (Test): 1
GNB - Accuracy (Test): 1
Ensemble - Accuracy (Test): 1
4.2 Multiclass 估计器

sklearn.multiclass可以处理多类别(multi-class) 的多标签(multi-label) 的分类问题。下面我们会使用数字数据集 digits 作为示例数据来讲解。我们先将数据分成 80:20 的训练集和测试集。
# 导入数据
from sklearn.datasets import load_digits
digits = load_digits()
digits.keys()
输出如下:
# 输出结果
dict_keys(['data', 'target', 'target_names','images', 'DESCR'])
下面我们切分数据集:
# 数据集切分
X_train, X_test, y_train, y_test = train_test_split( digits['data'], digits['target'], test_size=0.2 )
print( 'The size of X_train is ', X_train.shape )
print( 'The size of y_train is ', y_train.shape )
print( 'The size of X_test is ', X_test.shape )
print( 'The size of y_test is ', y_test.shape )
输出如下
The size of X_train is (1437, 64)
The size of y_train is (1437,)
The size of X_test is (360, 64)
The size of y_test is (360,)
训练集和测试集分别有 1437 和 360 张图像。每张照片是包含 8×8 的像素,我们用 flatten 操作把 2 维的 8×8 展平为 1 维的 64。
看看训练集中前 100 张图片和对应的标签(如下图)。像素很低,但基本上还是能看清。
fig, axes = plt.subplots( 10, 16, figsize=(8, 8) )
fig.subplots_adjust( hspace=0.1, wspace=0.1 )
for i, ax in enumerate( axes.flat ):
ax.imshow( X_train[i,:].reshape(8,8), cmap='binary’, interpolation='nearest’)
ax.text( 0.05, 0.05, str(y_train[i]),
transform=ax.transAxes, color='blue')
ax.set_xticks([])
ax.set_yticks([])

(1) 多类别分类
手写数字有 0-9 十类,但手头上只有二分类估计器(比如像支撑向量机)怎么用呢?我们可以采取以下策略处理:

- 一对一(One vs One,OvO):一个分类器用来处理数字 0 和数字 1,一个用来处理数字 0 和数字 2,一个用来处理数字 1 和 2,以此类推。N 个类需要 N(N-1)/2 个分类器。
- 一对其他(One vs All,OvA):训练 10 个二分类器,每一个对应一个数字,第一个分类「1」和「非 1」,第二个分类「2」和「非 2」,以此类推。N 个类需要 N 个分类器。
① OneVsOneClassifier
考虑一个具体天气多分类问题,天气可以是晴天、阴天和雨天,在 OvO 中,三个分类器为 f1、f2 和 f3。
- f1 负责区分橙色和绿色样本
- f2 负责区分橙色和紫色样本
- f3 负责区分绿色和紫色样本
在下图的例子中,f1 和 f2 都预测为橙色,f3 预测为紫色。根据多数原则得到的结合预测为橙色,如下图所示。

回到数字分类问题上,代码及结果如下:
from sklearn.multiclass import OneVsOneClassifier
from sklearn.linear_model import LogisticRegression
ovo_lr = OneVsOneClassifier( LogisticRegression(solver='lbfgs', max_iter=200) )
ovo_lr.fit( X_train, y_train )
OnevsOneClassifier(estimator=LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,intercept_scaling=1, max_iter=200, multi_class=‘warn’,n_jobs=None, penalty='12', random_state=None, solver='lbfgs’,tol=0.0001, verbose=6, warm_start=False),n_jobs=None)
10*9/2=45,10 类总共 45 个 OvO 分类器。
print( len(ovo_lr.estimators_) )
ovo_lr.estimators_
结果如下:
45
(LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,intercept_scaling=1, max_iter=200, multi_class='warn',n_jobs=None, penalty='12', random_state=None, solver='lbfgs',tol=60.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=200, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, solver='lbfgs',tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=200, multi_class='warn', n_jobs=None, penalty='12', random_state=None, solver='lbfgs', tol=60.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=200, multi_class='warn', n_jobs=None, penalty="12", random_state=None, solver='lbfgs', tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
...
训练集分类全对,测试集准确率 98%。
print ( “OvO LR - Accuracy (Train): %.4g" % metrics.accuracy_score(y_train, ovo_Ir.predict(X_train)) )
print ( "OvO LR - Accuracy (Test): %.4g" % metrics.accuracy_score(y_test, ovo_lr.predict(X_test}) )
# 运行结果
OvO LR - Accuracy (Train): 1
OvO LR - Accuracy (Test): 0.9806
② OneVsRestClassifier
在 OvA 中,把数据分成“某个”和“其他”
- 图一,某个=橙色,其他=绿色和紫色
- 图二,某个=绿色,其他=橙色和紫色
- 图三,某个=紫色,其他=橙色和绿色
三分类分解成三个二分类,对应的分类器为 f1、f2 和 f3。
- f1 预测负类,即预测绿色和紫色
- f2 预测负类,即预测橙色和紫色
- f3 预测正类,即预测紫色
三个分类器都预测了紫色,根据多数原则得到的预测是紫色,即阴天。

回到数字分类问题上,代码和结果如下:
from sklearn.multiclass import OneVsRestClassifier
ova_lr = OneVsRestClassifier( LogisticRegression(solver='lbfgs', max_iter=800) )
ova_lr.fit( X_train, y_train )
OnevsRestClassifier(estimator=LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=800, multi_class=‘warn’, n_jobs=None, penalty='12', random_state=None, solver='lbfgs’, tol=0.0001, verbose=6, warm_start=False), n_jobs=None)
10 类总共 10 个 OvA 分类器。
print( len(ova_lr.estimators_) )
ova_lr.estimators_
结果如下:
10
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=800, multi_class='warn', n_jobs=None, penalty='12', random_state=None, solver='lbfgs',tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=800, multi_class='warn', n_jobs=None, penalty='12', random_state=None, solver='lbfgs', tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, max_iter=800, multi_class=‘warn',
n_jobs=None, penalty='12', random_state=None, solver="lbfgs',
tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, max_iter=800, multi_class='warn', n_jobs=None, penalty='12', random_state=None, solver='lbfgs', tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
...
训练集准确率几乎 100%,测试集准确率 96%。代码与结果如下:
print ( “OvA LR - Accuracy (Train): %.4g" % metrics.accuracy_score(y_train, ova_Ir.predict(X_train)) )
print ( "OvA LR - Accuracy (Test): %.4g" % metrics.accuracy_score(y_test, ova_lr.predict(X_test}) )
OvA LR - Accuracy (Train): 6.9993
OvA LR - Accuracy (Test}: 6.9639
(2) 多标签分类
到目前为止,所有的样例都总是被分配到仅一个类。有些情况下,你也许想让分类器给一个样例输出多个类别。在无人驾驶的应用中,在下图识别出有车和指示牌,没有交通灯和人。
![SKLearn 最全应用指南; SKLearn 高级 API; Multiclass 估计器; 3-32
物体识别是一个复杂的深度学习问题,我们在这里暂且不深入探讨。我们先看一个简单点的例子,在手写数字的例子上,我们特意为每个数字设计了两个标签:
- 标签 1:奇数、偶数
- 标签 2:小于等于 4,大于 4
我们构建多标签y_train_multilabel,代码如下(OneVsRestClassifier 也可以用来做多标签分类):
from sklearn.multiclass import OneVsRestClassifier
y_train_multilabel = np.c_[y_train%2==0, y_train<=4 ]
print(y_train_multilabel)
[[ True True] [False False] [False False]
...
[False False] [False False] [False False]]
看下图训练集第 1 和 2 个图片是数字 4 和 5,对应上面两种标签结果为:
- [True True]:4 是偶数,小于等于 4
- [False False]:5 不是偶数,大于 4

我们这次用y_train_multilabel来训练模型。代码如下
ova_ml = OneVsRestClassifier( LogisticRegression(solver='lbfgs', max_iter=800) )
ova_ml.fit( X_train, y_train_multilabel )
# 运行结果
OnevsRestClassifier(estimator=LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=800, multi_class=‘warn’, n_jobs=None, penalty='12', random_state=None, solver='lbfgs', tol=0.0001, verbose=6, warm_start=False), n_jobs=None)
有两个估计器,每个对应一个标签。
print( len(ova_ml.estimators_) )
ova_ml.estimators_
运行结果如下:
2
[LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=800, multi_class=‘warn', n_jobs=None, penalty='12°, random_state=None, solver='lbfgs', tol=0.0001, verbose=0, warm_start=False),
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=800, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, solver='lbfgs', tol=0.0001, verbose=0, warm_start=False) ]
展示一下测试集上 100 张图片。
fig, axes = plt.subplots( 10, 10, figsize=(8, 8) )
fig.subplots_adjust( hspace=0.1, wspace=0.1 )
for i, ax in enumerate( axes.flat ):
ax.imshow( X_test[i,:].reshape(8,8), cmap='binary', interpolation='nearest')
ax.text( 6.05, 0.05, str(y_test[i]), transform=ax.transAxes, color='blue')
ax.set_xticks([])
ax.set_yticks([])

第一张图片是数字 2,它是偶数(标签 1 为 true),小于等于 4(标签 2 为 true)。
print( y_test[:1] )
print( ova_ml.predict(X_test[:1,:]) )
[2]
[[1 1]]
4.3 Multioutput 估计器
sklearn.multioutput可以处理多输出(multi-output)的分类问题。

多输出分类是多标签分类的泛化,在这里每一个标签可以是多类别(大于两个类别)的。一个例子就是预测图片每一个像素(标签)的像素值是多少(从 0 到 255 的 256 个类别)。

Multioutput 估计器有两个:
MultiOutputRegressor:多输出回归MultiOutputClassifier:多输出分类
这里我们只关注多输出分类。
(1) MultiOutputClassifier
首先引入 MultiOutputClassifier 和 RandomForestClassifier。
from sklearn.multioutput import MultiOutputClassifier
from sklearn.ensemble import RandomForestClassifier
在手写数字的例子上,我们也为特意每个数字设计了多标签而且每个标签的类别都大于二。
- 标签 1:小于等于 4,4 和 7 之间,大于等于 7(三类)
- 标签 2:数字本身(十类)
代码如下:
y_train_1st = y_train.copy()
y_train_1st[ y_train<=4 ] = 0
y_train_1st[ np.logical_and{y_train>4, y_train<7) ] = 1
y_train_ist[ y_train>=7 ] = 2
y_train_multioutput = np.c_[y_train_1st, y_train]
y_train_multioutput
# 运行结果
array( [[0, 4],
[1, 5],
[2, 7],
[1, 5],
[2, 9],
[2, 9]])
用含有 100 棵决策树的随机森林来解决这个多输入分类问题。
MO = MultiOutputClassifier( RandomForestClassifier(n_estimators=100) )
MO.fit( X_train, y_train_multioutput )
# 结果
MultiOutputClassifier(estimator=RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=None, max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=None, oob_score=False, random_state=None, verbose=0, warm_start=False), n_jobs=None)
看看这个模型在测试集前五张照片上的预测。
MO.predict( X_test[:5,:] )
array([[0, 2],[0, 2],[0, 0],[2, 9],[1, 5]])
这个 ndarray 第一列是标签 1 的类别,第二列是标签 2 的类别。预测结果是这五张照片分别显示数字 2、2、0、9、5(标签 2),它们前三个数 2、2、0 都小于等于 4(标签 1 第一类),第四个数 9 大于等于 7(标签 1 第二类),而第五个数 5 在 4 和 7 之间(标签 1 第三类)。

再看看真实标签。
y_test_1st = y_test.copy()
y_test_1st[ y_test<=4 ] = 0
y_test_1st[ np.logical_and(y_test>4, y_test<7) ] = 1
y_test_1st[ y_test>=7 ] = 2
y_test_multioutput = np.c_[ y_test_1st, y_test ]
y_test_multioutput[:5]
array([[0, 2],[0, 2],[0, 0],[2, 9],[1, 5]])
对比参考结果标签,模型预测的结果还是很不错的。
4.4 Model Selection 估计器

模型选择(Model Selction)在机器学习非常重要,它主要用于评估模型表现,常见的 Model Selection 估计器有以下几个:
cross_validate:评估交叉验证的结果。learning_curve:构建与绘制学习曲线。GridSearchCV:用交叉验证从超参数候选网格中搜索出最佳超参数。RandomizedSearchCV:用交叉验证从一组随机超参数搜索出最佳超参数。
这里我们只关注调节超参数的两个估计器,即GridSearchCV和RandomizedSearchCV。我们先回顾一下交叉验证(更详细的讲解请查看ShowMeAI文章 图解机器学习 | 模型评估方法与准则)。
(1) 交叉验证
K-折交叉验证(K-fold cross validation set),指的是把整个数据集平均但随机分成 K 份,每份大概包含 m/K 个数据(m 是总数据数)。
在这 K 份,每次选 K-1 份作为训练集拟合参数,在剩下 1 份验证集上进行评估计算。由于遍历了这 K 份数据,因此该操作称为交叉验证。操作如下图所示

下图展示了两个调参的估计器:「网格搜索」和「随机搜索」。

网格搜索调参:参数 1 在[1,10,100,1000]中取值,参数 2 在[0.01, 0.1, 1 10] 中取值,注意并不是等间距取值。模型在所有 16 组超参数上实验,选取交叉验证误差最小的参数。
随机搜索调参:根据指定分布随机搜索,可以选择独立于参数个数,比如 log(参数 1)服从 0 到 3 的均匀分布,log(参数 2)服从-2 到 1 的均匀分布。
应用方式与参考代码如下:
from time import time
from scipy.stats import randint
from sklearn.model_selection import GridSearchCv
from sklearn.model_selection import RandomizedSearchcCv
from sklearn.ensemble import RandomForestClassifier
X, y = digits.data, digits.target
RFC = RandomForestClassifier(n_estimators=20)
# 随机搜索/Randomized Search
param_dist = { "max_depth": [3, 5],
"max_features": randint(1, 11),
"min_samples_split": randint(2, 11),
"criterion": ["gini", "entropy"]}
n_iter_search = 20
random_search = RandomizedSearchCv( RFC, param_distributions=param_dist, n_iter=n_iter_search, cv=5 )}
start = time()
random_search.fit(X, y)
print("RandomizedSearchCv took %.2f seconds for %d candidates,parameter settings." % ((time() - start), n_iter_search))
print( random_search.best_params_ )
print( random_search.best_score_ )
# 网格搜索/Grid Search
param_grid = { "max_depth": [3, 5],
"max_features": [1, 3, 10],
"min_samples_ split": [2, 3, 10],
"criterion": ["gini", "entropy"]}
grid_search = GridSearchCV( RF, param_grid=param_grid, cv=5 )
start = time()
grid_search.fit(X, y)
print("\nGridSearchcv took %.2f seconds for %d candidate parameter settings." % (time() - start, len(grid_search.cv_results_['params'])))
print( grid_search.best_params_ )
print( grid_search.best_score_ )
输出结果如下:
RandomizedSearchCv took 3.73 seconds for 20 candidates parameter settings.
{'criterion': 'entropy', '*max_depth': 5, 'max_features': 6, 'min_samples_split': 4}
0.8898163606010017
GridSearchCV took 2.30 seconds for 36 candidate parameter settings.
{'criterion': 'entropy', 'max_depth': 5, 'max_features': 10, 'min_samples_ split': 10}
0.841402337228714S5
这里我们对代码做一个解释:
- 前 5 行引入相应工具库。
- 第 7-8 行准备好数据 X 和 y,创建一个含 20 个决策树的随机森林模型。
- 第 10-14 和 23-27 行为对随机森林的超参数「最大树深、最多特征数、最小可分裂样本数、分裂标准」构建候选参数分布与参数网格。
- 第 15-18 行是运行随机搜索。
- 第 18-30 行是运行网格搜索。
运行结果里:
- 第一行输出每种追踪法运行的多少次和花的时间。
- 第二行输出最佳超参数的组合。
- 第三行输出最高得分。
在本例中,随机搜索比网格搜索用更短时间内找到一组超参数,获得了更高的得分。
4.5 Pipeline 估计器
Pipeline 估计器又叫流水线,把各种估计器串联(Pipeline)或并联(FeatureUnion)的方式组成一条龙服务。用好了它真的能大大提高效率。

(1) Pipeline
Pipeline 将若干个估计器按顺序连在一起,比如:特征提取 → 降维 → 拟合 → 预测
Pipeline 的属性永远和最后一个估计器属性一样:
- 如果最后一个估计器是预测器,那么 Pipeline 是预测器。
- 如果最后一个估计器是转换器,那么 Pipeline 是转换器。
下面是一个简单示例,使用 Pipeline 来完成「填补缺失值-标准化」这两步的。我们先构建含缺失值 NaN 的数据 X。
X = np.array([[56,40,30,5,7,10,9,np.NaN,12],
[1.68,1.83,1.77,np.NaN,1.9,1.65,1.88,np.NaN,1.75]])
X = np.transpose(X)
我们用以下流程组件构建 Pipeline:
- 处理缺失值的转换器 SimpleImputer。
- 做规划化的转换器 MinMaxScaler。
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import MinMaxScaler
pipe = Pipeline([
('impute', SimpleImputer(missing_values=np.nan, strategy='mean')),
('normalize', MinMaxScaler())])
第 5-7 行创建了流水线,使用方式非常简单,在Pipeline()里输入(名称,估计器)这个元组构建的流水线列表。在本例中 SimpleImputer 起名叫 impute,MinMaxScaler 起名叫 normalize。
因为最后一个估计器是转换器,因此 pipeline 也是个转换器。下面我们来运行一下,我们发现值都被填满了,而且两列也被标准化了。
X_proc = pipe.fit_transform( X )
来验证上面流水线的参数,我们可以按顺序来运行这两个转换器,结果是一样的。
X_impute = SimpleImputer(missing values=np.nan, strategy='mean').fit_transform( X )
X_impute
# 运行结果
array( [[50, 1.68],
[40, 1.83],
[30, 1.77],
[5, 1.78],
[7, 1.9 ],
[10, 1.65],
[9, 1.88],
[20.375, 1.78],
[12, 1.75 ]])
X_normalize = MinMaxScaler().fit_transform( X_impute )
X_normalize
运行结果
array( [[1., 0.12 ],
[0.77777778, 0.72],
[0.55555556, 6.48],
[0.52, 1],
[0.04444444, 1.],
[0.11111111, 9.],
[0.08888889, 6.92],
[0.34166667, 6.52],
[0.15555556, 0.4 ]])
(2) FeatureUnion
如果我们想在一个节点同时运行几个估计器,我们可用 FeatureUnion。在下面的例子中,我们首先建立一个 DataFrame 数据,它有如下特点:
- 前两列字段「智力 IQ」和「脾气 temper」都是类别型变量。
- 后两列字段「收入 income」和「身高 height」都是数值型变量。
- 每列中都有缺失值。
d= { 'IQ' : ['high','avg','avg','low', high', avg', 'high', 'high',None],
'temper' : ['good', None,'good', 'bad', 'bad','bad', 'bad', None, 'bad'],
'income' : [50,40,30,5,7,10,9,np.NaN,12],
'height' : [1.68,1.83,1.77,np.NaN,1.9,1.65,1.88,np.NaN,1.75]}
X = pd.DataFrame( d )
X
结果如下:

我们现在按下列步骤来清洗数据。
- 对类别型变量:获取数据 → 中位数填充 → 独热编码
- 对数值型变量:获取数据 → 均值填充 → 标准化
上面两步是并行进行的。
首先我们自己定义一个从 DataFrame 里面获取数据列的类,起名叫DataFrameSelector。
from sklearn.base import BaseEstimator, TransformerMixin
class DataFrameSelector( BaseEstimator, TransformerMixin ):
def _init_( self, attribute_names ):
self.attribute_names = attribute_names
def fit( self, X, y=None ):
return self
def transform( self, X ):
return X[self.attribute_names].values
上述代码在 transform 函数中,我们将输入的 DataFrame X 根据属性名称来获取其值。
接下来建立流水线full_pipe,它并联着两个流水线
-
categorical_pipe 处理分类型变量
- DataFrameSelector 用来获取
- SimpleImputer 用出现最多的值来填充 None
- OneHotEncoder 来编码返回非稀疏矩阵
-
numeric_pipe 处理数值型变量
- DataFrameSelector 用来获取
- SimpleImputer 用均值来填充 NaN
- normalize 来规范化数值
代码如下:
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import OneHotEncoder
categorical features = ['IQ', 'temper']
numeric_features = ['income', 'height']
categorical pipe = Pipeline([
('select', DataFrameSelector(categorical_features)),
('impute', SimpleImputer(missing values=None, strategy='most_frequent')),
('one_hot_encode', OneHotEncoder(sparse=False))])
numeric_pipe = Pipeline([
('select', DataFrameSelector(numeric_features)),
('impute', SimpleImputer(missing values=np.nan, strategy='mean')),
('normalize', MinMaxScaler())])
full_pipe = FeatureUnion( transformer_list=[
('numeric_pipe', numeric_pipe),
('categorical_pipe', categorical_pipe)])
我们打印结果如下:
X_proc = full_pipe.fit_transform( X )
print( X_proc )
[[1\. 0.12 0\. 1\. 0\. 0\. 1\. ]
[0.77777778 0.72 1\. 0\. 0\. 1\. 0\. ]
[0.55555556 0.48 1\. 0\. 0\. 0\. 1\. ]
[0\. 0.52 0\. 0\. 1\. 1\. 0\. ]
[0.04444444 1\. 0\. 1\. 0\. 1\. 0\. ]
[0.11111111 0\. 1\. 0\. 0\. 1\. 0\. ]
[0.08888889 0.92 0\. 1\. 0\. 1\. 0\. ]
[0.34166667 0.52 0\. 1\. 0\. 1\. 0\. ]
[0.15555556 0.4 0\. 1\. 0\. 1\. 0\. ]]
5.总结
下面我们对上面讲解到的 sklearn 工具库应用知识做一个总结。
5.1 SKLearn 五大原则
SKLearn 的设计下,它的主要 API 遵循五大原则
(1) 一致性
所有对象的接口一致且简单,在「估计器」中
- 创建:
model = Constructor(hyperparam) - 拟参:
- 有监督学习:
model.fit(X_train, y_train) - 无监督学习:
model.fit(X_train)
- 有监督学习:
在「预测器」中
- 有监督学习里预测标签:
y_pred = model.predict(X_test) - 无监督学习里识别模式:
idx_pred = model.predict( Xtest)
在「转换器」中
- 创建:
trm = Constructor(hyperparam) - 获参:
trm.fit(X_train) - 转换:
X_trm = trm.transform(X_train)
(2) 可检验
所有估计器里设置的超参数和学到的参数都可以通过实例的变量直接访问来检验其值,区别是超参数的名称最后没有下划线 _,而参数的名称最后有下划线 _。举例如下:
- 通例:
model.hyperparameter - 特例:
SVC.kernel - 通例:
model.parameter_ - 特例:
SVC.support_vectors_
(3) 标准类
SKLearn 模型接受的数据集的格式只能是「Numpy 数组」和「Scipy 稀疏矩阵」。超参数的格式只能是「字符」和「数值」。
不接受其他的类!
(4) 可组成
模块都能重复「连在一起」或「并在一起」使用,比如两种形式流水线(pipeline)
- 任意转换器序列
- 任意转换器序列+估计器
(5) 有默认
SKLearn 给大多超参数提供了合理的默认值,大大降低了建模的难度。
5.2 SKLearn 框架流程
sklearn 的建模应用流程框架大概如下:
(1) 确定任务
是「有监督」的分类或回归?还是「无监督」的聚类或降维?确定好后基本就能知道用 Sklearn 里哪些模型了。
(2) 数据预处理
这步最繁琐,要处理缺失值、异常值;要编码类别型变量;要正规化或标准化数值型变量,等等。但是有了 Pipeline 神器一切变得简单高效。
(3) 训练和评估
这步最简单,训练用估计器fit()先拟合,评估用预测器predict()来评估。
(4) 选择模型
启动 ModelSelection 估计器里的 GridSearchCV 和 RandomizedSearchCV,选择得分最高的那组超参数(即模型)。
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | XGBoost 建模应用详解

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/204
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
XGBoost 是 eXtreme Gradient Boosting 的缩写称呼,它是一个非常强大的 Boosting 算法工具包,优秀的性能(效果与速度)让其在很长一段时间内霸屏数据科学比赛解决方案榜首,现在很多大厂的机器学习方案依旧会首选这个模型。XGBoost 在并行计算效率、缺失值处理、控制过拟合、预测泛化能力上都变现非常优秀。
本篇内容 ShowMeAI 展开给大家讲解 XGBoost 的工程应用方法,对于 XGBoost 原理知识感兴趣的同学,欢迎参考 ShowMeAI 的另外一篇原理文章 图解机器学习|XGBoost 模型详解。
1.XGBoost 安装
XGBoost 作为常见的强大 Python 机器学习工具库,安装也比较简单。
1.1 Python 与 IDE 环境设置

python 环境与 IDE 设置可以参考ShowMeAI文章 图解 python | 安装与环境设置 进行设置。
1.2 工具库安装
(1) Linux/Mac 等系统
这些系统下的 XGBoost 安装,大家只要基于 pip 就可以轻松完成了,在命令行端输入命令如下命令即可等待安装完成。
pip install xgboost
大家也可以选择国内的 pip 源,以获得更好的安装速度
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xgboost

(2) Windows 系统
对于 windows 系统而言,比较高效便捷的安装方式是:在网址www.lfd.uci.edu/~gohlke/pythonlibs/ 中去下载对应版本的的 XGBoost 安装包,再通过如下命令安装。
pip install xgboost‑1.5.1‑cp310‑cp310‑win32.whl
2.XGBoost 数据读取
应用 XGBoost 的第一步,需要加载所需的数据成为工具库所能支持的格式形态。XGBoost 可以加载多种数据格式的数据用于训练建模:
- libsvm 格式的文本数据。
- Numpy 的二维数组。
- XGBoost 的二进制的缓存文件。加载的数据存储在对象 DMatrix 中。
XGBoost 的 SKLearn 接口也支持对于 Dataframe 格式的数据(参考ShowMeAI的文章 Python 数据分析|Pandas 核心操作函数大全 进行更多了解)进行处理。
下面是不同格式的数据,XGBoost 的加载方式。
- 加载 libsvm 格式的数据
dtrain1 = xgb.DMatrix('train.svm.txt')
- 加载二进制的缓存文件
dtrain2 = xgb.DMatrix('train.svm.buffer')
- 加载 numpy 的数组
data = np.random.rand(5,10) # 5 entities, each contains 10 features
label = np.random.randint(2, size=5) # binary target
dtrain = xgb.DMatrix( data, label=label)
- 将 scipy.sparse 格式的数据转化为 DMatrix 格式
csr = scipy.sparse.csr_matrix( (dat, (row,col)) )
dtrain = xgb.DMatrix( csr )
- 将 DMatrix 格式的数据保存成 XGBoost 的二进制格式,在下次加载时可以提高加载速度,使用方式如下
dtrain = xgb.DMatrix('train.svm.txt')
dtrain.save_binary("train.buffer")
- 可以用如下方式处理 DMatrix 中的缺失值
dtrain = xgb.DMatrix( data, label=label, missing = -999.0)
- 当需要给样本设置权重时,可以用如下方式
w = np.random.rand(5,1)
dtrain = xgb.DMatrix( data, label=label, missing = -999.0, weight=w)
3.XGBoost 不同建模方式
3.1 内置建模方式:libsvm 格式数据源
XGBoost 内置了建模方式,有如下的数据格式与核心训练方法:
- 基于
DMatrix格式的数据。 - 基于
xgb.train接口训练。
下面是官方的一个简单示例,演示了读取 libsvm 格式数据(成DMatrix格式)并指定参数建模的过程。
# 导入工具库
import numpy as np
import scipy.sparse
import pickle
import xgboost as xgb
# 从 libsvm 文件中读取数据,做二分类
# 数据是 libsvm 的格式,如下样本格式
#1 3:1 10:1 11:1 21:1 30:1 34:1 36:1 40:1 41:1 53:1 58:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 105:1 117:1 124:1
#0 3:1 10:1 20:1 21:1 23:1 34:1 36:1 39:1 41:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 120:1
#0 1:1 10:1 19:1 21:1 24:1 34:1 36:1 39:1 42:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 122:1
dtrain = xgb.DMatrix('./data/agaricus.txt.train')
dtest = xgb.DMatrix('./data/agaricus.txt.test')
# 超参数设定
# 主要是树深、学习率、目标函数
param = {'max_depth':2, 'eta':1, 'silent':1, 'objective':'binary:logistic' }
# 设定 watchlist 用于建模过程中观测模型状态
watchlist = [(dtest,'eval'), (dtrain,'train')]
num_round = 2
bst = xgb.train(param, dtrain, num_round, watchlist)
# 使用模型预测
preds = bst.predict(dtest)
# 判断准确率
labels = dtest.get_label()
print('错误率为%f' % \
(sum(1 for i in range(len(preds)) if int(preds[i]>0.5)!=labels[i]) /float(len(preds))))
# 模型存储
bst.save_model('./model/0001.model')

[0] eval-error:0.042831 train-error:0.046522
[1] eval-error:0.021726 train-error:0.022263
错误率为 0.021726
3.2 内置建模方式:csv 格式数据源
下面的例子,输入的数据源是 csv 文件,我们使用大家熟悉的 pandas 工具库(参考ShowMeAI教程 数据分析系列教程 与 数据科学工具速查 | Pandas 使用指南)把数据读取为 Dataframe 格式,再构建 Dmatrix 格式输入,后续使用内置建模方式进行训练。
# 皮马印第安人糖尿病数据集 包含很多字段:怀孕次数 口服葡萄糖耐量试验中血浆葡萄糖浓度 舒张压(mm Hg) 三头肌组织褶厚度(mm)
# 2 小时血清胰岛素(μU/ ml) 体重指数(kg/(身高(m)²) 糖尿病系统功能 年龄(岁)
import pandas as pd
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')
data.head()

# 导入工具库
import numpy as np
import pandas as pd
import pickle
import xgboost as xgb
from sklearn.model_selection import train_test_split
# 用 pandas 读入数据
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')
# 做数据切分
train, test = train_test_split(data)
# 转换成 Dmatrix 格式
feature_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
target_column = 'Outcome'
# 取出 Dataframe 的 numpy 数组值去初始化 DMatrix 对象
xgtrain = xgb.DMatrix(train[feature_columns].values, train[target_column].values)
xgtest = xgb.DMatrix(test[feature_columns].values, test[target_column].values)
#参数设定
param = {'max_depth':5, 'eta':0.1, 'silent':1, 'subsample':0.7, 'colsample_bytree':0.7, 'objective':'binary:logistic' }
# 设定 watchlist 用于查看模型状态
watchlist = [(xgtest,'eval'), (xgtrain,'train')]
num_round = 10
bst = xgb.train(param, xgtrain, num_round, watchlist)
# 使用模型预测
preds = bst.predict(xgtest)
# 判断准确率
labels = xgtest.get_label()
print('错误类为%f' % \
(sum(1 for i in range(len(preds)) if int(preds[i]>0.5)!=labels[i]) /float(len(preds))))
# 模型存储
bst.save_model('./model/0002.model')

[0] eval-error:0.354167 train-error:0.194444
[1] eval-error:0.34375 train-error:0.170139
[2] eval-error:0.322917 train-error:0.170139
[3] eval-error:0.28125 train-error:0.161458
[4] eval-error:0.302083 train-error:0.147569
[5] eval-error:0.286458 train-error:0.138889
[6] eval-error:0.296875 train-error:0.142361
[7] eval-error:0.291667 train-error:0.144097
[8] eval-error:0.302083 train-error:0.130208
[9] eval-error:0.291667 train-error:0.130208
错误类为 0.291667
3.3 预估器建模方式:SKLearn 接口+Dataframe
XGBoost 也支持用 SKLearn 中统一的预估器形态接口进行建模,如下为典型的参考案例,对于读取为 Dataframe 格式的训练集和测试集,可以直接使用 XGBoost 初始化 XGBClassifier 进行 fit 拟合训练。使用方法与接口,和 SKLearn 中其他预估器一致。
# 导入工具库
import numpy as np
import pandas as pd
import pickle
import xgboost as xgb
from sklearn.model_selection import train_test_split
# 用 pandas 读入数据
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')
# 做数据切分
train, test = train_test_split(data)
# 特征列
feature_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
# 标签列
target_column = 'Outcome'
# 初始化模型
xgb_classifier = xgb.XGBClassifier(n_estimators=20,\
max_depth=4, \
learning_rate=0.1, \
subsample=0.7, \
colsample_bytree=0.7, \
eval_metric='error')
# Dataframe 格式数据拟合模型
xgb_classifier.fit(train[feature_columns], train[target_column])
# 使用模型预测
preds = xgb_classifier.predict(test[feature_columns])
# 判断准确率
print('错误类为%f' %((preds!=test[target_column]).sum()/float(test_y.shape[0])))
# 模型存储
joblib.dump(xgb_classifier, './model/0003.model')

错误类为 0.265625
['./model/0003.model']
4.模型调参与高级功能
4.1 XGBoost 参数详解
在运行 XGBoost 之前,必须设置三种类型成熟:general parameters,booster parameters 和 task parameters:

-
通用参数:General parameters
- 该参数控制在提升(boosting)过程中使用哪种 booster,常用的 booster 有树模型(tree)和线性模型(linear model)。
-
提升器参数:Booster parameters
- 这取决于使用哪种 booster,包含树模型 booster 和线性 booster 参数。
-
任务参数:Task parameters
- 控制学习的场景,例如在回归问题中会使用不同的参数控制排序。
(1) 通用参数

- booster [default=gbtree]
有两种模型可以选择 gbtree 和 gblinear。gbtree 使用基于树的模型进行提升计算,gblinear 使用线性模型进行提升计算。缺省值为 gbtree
- silent [default=0]
取 0 时表示打印出运行时信息,取 1 时表示以缄默方式运行,不打印运行时信息。缺省值为 0
- nthread
XGBoost 运行时的线程数。缺省值是当前系统可以获得的最大线程数
- num_pbuffer
预测缓冲区大小,通常设置为训练实例的数目。缓冲用于保存最后一步提升的预测结果,无需人为设置。
- num_feature
Boosting 过程中用到的特征维数,设置为特征个数。XGBoost 会自动设置,无需人为设置。
(2) 树模型 booster 参数

- eta [default=0.3]
为了防止过拟合,更新过程中用到的收缩步长。在每次提升计算之后,算法会直接获得新特征的权重。 eta 通过缩减特征的权重使提升计算过程更加保守。缺省值为 0.3 取值范围为:[0,1]
- gamma [default=0]
树要进一步分裂生长所需的最小 loss 减小值. the larger, the more conservative the algorithm will be. 取值范围为:[0,∞]
- max_depth [default=6]
数的最大深度。缺省值为 6 取值范围为:[1,∞]
- min_child_weight [default=1]
孩子节点中最小的样本权重和。如果一个叶子节点的样本权重和小于 min_child_weight 则拆分过程结束。在现行回归模型中,这个参数是指建立每个模型所需要的最小样本数。该成熟越大算法越 conservative 取值范围为:[0,∞]
- max_delta_step [default=0]
我们允许每个树的权重被估计的值。如果它的值被设置为 0,意味着没有约束;如果它被设置为一个正值,它能够使得更新的步骤更加保守。通常这个参数是没有必要的,但是如果在逻辑回归中类极其不平衡这时候他有可能会起到帮助作用。把它范围设置为 1-10 之间也许能控制更新。 取值范围为:[0,∞]
- subsample [default=1]
用于训练模型的子样本占整个样本集合的比例。如果设置为 0.5 则意味着 XGBoost 将随机的从整个样本集合中随机的抽取出 50%的子样本建立树模型,这能够防止过拟合。 取值范围为:(0,1]
- colsample_bytree [default=1]
在建立树时对特征采样的比例。缺省值为 1 取值范围为:(0,1]
(3) 线性 Booster 参数

- lambda [default=0]
L2 正则的惩罚系数
- alpha [default=0]
L1 正则的惩罚系数
- lambda_bias
在偏置上的 L2 正则。缺省值为 0(在 L1 上没有偏置项的正则,因为 L1 时偏置不重要)
(4) 任务参数

-
objective [ default=reg:linear ]
- 定义学习任务及相应的学习目标
- 可选的目标函数如下:
reg:linear: 线性回归。reg:logistic: 逻辑回归。binary:logistic: 二分类的逻辑回归问题,输出为概率。binary:logitraw: 二分类的逻辑回归问题,输出的结果为 wTx。count:poisson: 计数问题的 poisson 回归,输出结果为 poisson 分布。在 poisson 回归中,max_delta_step 的缺省值为 0.7。(used to safeguard optimization)。multi:softmax:让 XGBoost 采用 softmax 目标函数处理多分类问题,同时需要设置参数 num_class(类别个数)。multi:softprob:和 softmax 一样,但是输出的是 ndata * nclass 的向量,可以将该向量 reshape 成 ndata 行 nclass 列的矩阵。没行数据表示样本所属于每个类别的概率。rank:pairwise:set XGBoost to do ranking task by minimizing the pairwise loss。
-
base_score [ default=0.5 ]
- 所有实例的初始化预测分数,全局偏置;
- 为了足够的迭代次数,改变这个值将不会有太大的影响。
-
eval_metric [ default according to objective ]
- 校验数据所需要的评价指标,不同的目标函数将会有缺省的评价指标(rmse for regression, and error for classification, mean average precision for ranking)
- 用户可以添加多种评价指标,对于 Python 用户要以 list 传递参数对给程序,而不是 map 参数 list 参数不会覆盖`eval_metric’
- 可供的选择如下:
rmse:root mean square errorlogloss:negative log-likelihooderror:Binary classification error rate. It is calculated as #(wrong cases)/#(all cases). For the predictions, the evaluation will regard the instances with prediction value larger than 0.5 as positive instances, and the others as negative instances.merror:Multiclass classification error rate. It is calculated as #(wrongcases)#(allcases).mlogloss:Multiclass loglossauc:Area under the curve for ranking evaluation.ndcg:Normalized Discounted Cumulative Gainmap:Mean average precisionndcg@n,map@n:n can be assigned as an integer to cut off the top positions in the lists for evaluation.ndcg-,map-,ndcg@n-,map@n-:In XGBoost, NDCG and MAP will evaluate the score of a list without any positive samples as 1. By adding-in the evaluation metric XGBoost will evaluate these score as 0 to be consistent under some conditions. training repeatively
-
seed [ default=0 ]
- 随机数的种子。缺省值为 0
4.2 内置调参优化
(1) 交叉验证
XGBoost 自带实验与调参的一些方法,如下为交叉验证方法xgb.cv。
xgb.cv(param, dtrain, num_round, nfold=5,metrics={'error'}, seed = 0)

(2) 添加预处理
我们可以把数据建模过程中的一些设置加到交叉验证环节里,比如对于不同类别的样本加权,可以参考下列代码示例
# 计算正负样本比,调整样本权重
def fpreproc(dtrain, dtest, param):
label = dtrain.get_label()
ratio = float(np.sum(label == 0)) / np.sum(label==1)
param['scale_pos_weight'] = ratio
return (dtrain, dtest, param)
# 先做预处理,计算样本权重,再做交叉验证
xgb.cv(param, dtrain, num_round, nfold=5,
metrics={'auc'}, seed = 0, fpreproc = fpreproc)


(3) 自定义损失函数与评估准则
XGBoost 支持在训练过程中,自定义损失函数和评估准则,其中损失函数的定义需要返回损失函数一阶和二阶导数的计算方法,评估准则部分需要对数据的 label 和预估值进行计算。其中损失函数用于训练过程中的树结构学习,而评估准则很多时候是用在验证集上进行效果评估。
print('使用自定义损失函数进行交叉验证')
# 自定义损失函数,需要提供损失函数的一阶导和二阶导
def logregobj(preds, dtrain):
labels = dtrain.get_label()
preds = 1.0 / (1.0 + np.exp(-preds))
grad = preds - labels
hess = preds * (1.0-preds)
return grad, hess
# 自定义评估准则,评估预估值和标准答案之间的差距
def evalerror(preds, dtrain):
labels = dtrain.get_label()
return 'error', float(sum(labels != (preds > 0.0))) / len(labels)
watchlist = [(dtest,'eval'), (dtrain,'train')]
param = {'max_depth':3, 'eta':0.1, 'silent':1}
num_round = 5
# 自定义损失函数训练
bst = xgb.train(param, dtrain, num_round, watchlist, logregobj, evalerror)
# 交叉验证
xgb.cv(param, dtrain, num_round, nfold = 5, seed = 0, obj = logregobj, feval=evalerror)


使用自定义损失函数进行交叉验证
[0] eval-rmse:0.306901 train-rmse:0.306164 eval-error:0.518312 train-error:0.517887
[1] eval-rmse:0.179189 train-rmse:0.177278 eval-error:0.518312 train-error:0.517887
[2] eval-rmse:0.172565 train-rmse:0.171728 eval-error:0.016139 train-error:0.014433
[3] eval-rmse:0.269612 train-rmse:0.27111 eval-error:0.016139 train-error:0.014433
[4] eval-rmse:0.396903 train-rmse:0.398256 eval-error:0.016139 train-error:0.014433
(4) 只用前 n 颗树预测
对于 boosting 模型来说,最后会训练得到很多基学习器(在 XGBoost 中很多时候是很多棵树),我们可以一次完整训练,只用前 n 棵树的集成来完成预测。
#!/usr/bin/python
import numpy as np
import pandas as pd
import pickle
import xgboost as xgb
from sklearn.model_selection import train_test_split
# 基本例子,从 csv 文件中读取数据,做二分类
# 用 pandas 读入数据
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')
# 做数据切分
train, test = train_test_split(data)
# 转换成 Dmatrix 格式
feature_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
target_column = 'Outcome'
xgtrain = xgb.DMatrix(train[feature_columns].values, train[target_column].values)
xgtest = xgb.DMatrix(test[feature_columns].values, test[target_column].values)
#参数设定
param = {'max_depth':5, 'eta':0.1, 'silent':1, 'subsample':0.7, 'colsample_bytree':0.7, 'objective':'binary:logistic' }
# 设定 watchlist 用于查看模型状态
watchlist = [(xgtest,'eval'), (xgtrain,'train')]
num_round = 10
bst = xgb.train(param, xgtrain, num_round, watchlist)
# 只用第 1 颗树预测
ypred1 = bst.predict(xgtest, ntree_limit=1)
# 用前 9 颗树预测
ypred2 = bst.predict(xgtest, ntree_limit=9)
label = xgtest.get_label()
print('用前 1 颗树预测的错误率为 %f' % (np.sum((ypred1>0.5)!=label) /float(len(label))))
print('用前 9 颗树预测的错误率为 %f' % (np.sum((ypred2>0.5)!=label) /float(len(label))))

[0] eval-error:0.255208 train-error:0.196181
[1] eval-error:0.234375 train-error:0.175347
[2] eval-error:0.25 train-error:0.163194
[3] eval-error:0.229167 train-error:0.149306
[4] eval-error:0.213542 train-error:0.154514
[5] eval-error:0.21875 train-error:0.152778
[6] eval-error:0.21875 train-error:0.154514
[7] eval-error:0.213542 train-error:0.138889
[8] eval-error:0.1875 train-error:0.147569
[9] eval-error:0.1875 train-error:0.144097
用前 1 颗树预测的错误率为 0.255208
用前 9 颗树预测的错误率为 0.187500
4.3 预估器调参优化
(1) SKLearn 形态接口实验评估
XGBoost 有 SKLearn 预估器形态的接口,整体使用方法和 SKLearn 中其他预估器一致,如下是手动对数据做交叉验证,注意到这里直接使用XGBClassifier对 Dataframe 数据进行 fit 拟合和评估。
import pickle
import xgboost as xgb
import numpy as np
from sklearn.model_selection import KFold, train_test_split, GridSearchCV
from sklearn.metrics import confusion_matrix, mean_squared_error
from sklearn.datasets import load_iris, load_digits, load_boston
rng = np.random.RandomState(31337)
# 二分类:混淆矩阵
print("数字 0 和 1 的二分类问题")
digits = load_digits(2)
y = digits['target']
X = digits['data']
# 数据切分对象
kf = KFold(n_splits=2, shuffle=True, random_state=rng)
print("在 2 折数据上的交叉验证")
# 2 折交叉验证
for train_index, test_index in kf.split(X):
xgb_model = xgb.XGBClassifier().fit(X[train_index],y[train_index])
predictions = xgb_model.predict(X[test_index])
actuals = y[test_index]
print("混淆矩阵:")
print(confusion_matrix(actuals, predictions))
#多分类:混淆矩阵
print("\nIris: 多分类")
iris = load_iris()
y = iris['target']
X = iris['data']
kf = KFold(n_splits=2, shuffle=True, random_state=rng)
print("在 2 折数据上的交叉验证")
for train_index, test_index in kf.split(X):
xgb_model = xgb.XGBClassifier().fit(X[train_index],y[train_index])
predictions = xgb_model.predict(X[test_index])
actuals = y[test_index]
print("混淆矩阵:")
print(confusion_matrix(actuals, predictions))
#回归问题:MSE
print("\n 波士顿房价回归预测问题")
boston = load_boston()
y = boston['target']
X = boston['data']
kf = KFold(n_splits=2, shuffle=True, random_state=rng)
print("在 2 折数据上的交叉验证")
for train_index, test_index in kf.split(X):
xgb_model = xgb.XGBRegressor().fit(X[train_index],y[train_index])
predictions = xgb_model.predict(X[test_index])
actuals = y[test_index]
print("MSE:",mean_squared_error(actuals, predictions))
数字 0 和 1 的二分类问题
在 2 折数据上的交叉验证
混淆矩阵:
[[87 0]
[ 1 92]]
混淆矩阵:
[[91 0]
[ 3 86]]
Iris: 多分类
在 2 折数据上的交叉验证
混淆矩阵:
[[19 0 0]
[ 0 31 3]
[ 0 1 21]]
混淆矩阵:
[[31 0 0]
[ 0 16 0]
[ 0 3 25]]
波士顿房价回归预测问题
在 2 折数据上的交叉验证
MSE: 9.860776812557337
MSE: 15.942418468446029
(2) 网格搜索调参
上面提到 XGBoost 的预估器接口,整体使用方法和 SKLearn 中其他预估器一致,所以我们也可以使用 SKLearn 中的超参数调优方法来进行模型调优。
如下是一个典型的网格搜索交法调优超参数的代码示例,我们会给出候选参数列表字典,通过GridSearchCV进行交叉验证实验评估,选出 XGBoost 在候选参数中最优的超参数。
print(“参数最优化:”)
y = boston['target']
X = boston['data']
xgb_model = xgb.XGBRegressor()
clf = GridSearchCV(xgb_model,
{'max_depth': [2,4,6],
'n_estimators': [50,100,200]}, verbose=1)
clf.fit(X,y)
print(clf.best_score_)
print(clf.best_params_)

参数最优化:
Fitting 3 folds for each of 9 candidates, totalling 27 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
0.6001029721598573
{'max_depth': 4, 'n_estimators': 100}
[Parallel(n_jobs=1)]: Done 27 out of 27 | elapsed: 1.3s finished
(3) early-stopping 早停
XGBoost 模型有时候会因为不停叠加新的树(修正训练集上拟合尚不正确的一些样本),可能会因为对于训练集过度学习而导致模型过拟合。early stopping 早停止是一个有效的策略,具体的做法是,在训练集不断追加树学习的过程中,对验证集上的表现进行监控,如果出现一定轮次评估准则都没有优化提升的情况,则回溯到历史上验证集最好的点,保存为最佳模型。
下面是对应的代码示例,其中参数early_stopping_rounds设定了验证集上能接受的效果不提升的最多轮次数,eval_set指定了验证数据集。
# 在训练集上学习模型,一颗一颗树添加,在验证集上看效果,当验证集效果不再提升,停止树的添加与生长
X = digits['data']
y = digits['target']
X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=0)
clf = xgb.XGBClassifier()
clf.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="auc",
eval_set=[(X_val, y_val)])

[0] validation_0-auc:0.999497
Will train until validation_0-auc hasn't improved in 10 rounds.
[1] validation_0-auc:0.999497
[2] validation_0-auc:0.999497
[3] validation_0-auc:0.999749
[4] validation_0-auc:0.999749
[5] validation_0-auc:0.999749
[6] validation_0-auc:0.999749
[7] validation_0-auc:0.999749
[8] validation_0-auc:0.999749
[9] validation_0-auc:0.999749
[10] validation_0-auc:1
[11] validation_0-auc:1
[12] validation_0-auc:1
[13] validation_0-auc:1
[14] validation_0-auc:1
[15] validation_0-auc:1
[16] validation_0-auc:1
[17] validation_0-auc:1
[18] validation_0-auc:1
[19] validation_0-auc:1
[20] validation_0-auc:1
Stopping. Best iteration:
[10] validation_0-auc:1
XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0,
max_depth=3, min_child_weight=1, missing=None, n_estimators=100,
n_jobs=1, nthread=None, objective='binary:logistic', random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
silent=True, subsample=1)
(4) 特征重要度
XGBoost 建模过程中,还可以学习到对应的特征重要度信息,并保存在模型的feature_importances_属性中。如下为绘制特征重要度的可视化代码:
iris = load_iris()
y = iris['target']
X = iris['data']
xgb_model = xgb.XGBClassifier().fit(X,y)
print('特征排序:')
feature_names=['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
feature_importances = xgb_model.feature_importances_
indices = np.argsort(feature_importances)[::-1]
for index in indices:
print("特征 %s 重要度为 %f" %(feature_names[index], feature_importances[index]))
%matplotlib inline
import matplotlib.pyplot as plt
plt.figure(figsize=(16,8))
plt.title("feature importances")
plt.bar(range(len(feature_importances)), feature_importances[indices], color='b')
plt.xticks(range(len(feature_importances)), np.array(feature_names)[indices], color='b')

特征排序:
特征 petal_length 重要度为 0.415567
特征 petal_width 重要度为 0.291557
特征 sepal_length 重要度为 0.179420
特征 sepal_width 重要度为 0.113456

(5) 并行训练加速
在多资源的情况下,XGBoost 可以实现并行训练加速,示例代码如下:
import os
if __name__ == "__main__":
try:
from multiprocessing import set_start_method
except ImportError:
raise ImportError("Unable to import multiprocessing.set_start_method."
" This example only runs on Python 3.4")
#set_start_method("forkserver")
import numpy as np
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_boston
import xgboost as xgb
rng = np.random.RandomState(31337)
print("Parallel Parameter optimization")
boston = load_boston()
os.environ["OMP_NUM_THREADS"] = "2" # or to whatever you want
y = boston['target']
X = boston['data']
xgb_model = xgb.XGBRegressor()
clf = GridSearchCV(xgb_model, {'max_depth': [2, 4, 6],
'n_estimators': [50, 100, 200]}, verbose=1,
n_jobs=2)
clf.fit(X, y)
print(clf.best_score_)
print(clf.best_params_)
Parallel Parameter optimization
Fitting 3 folds for each of 9 candidates, totalling 27 fits
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 24 out of 27 | elapsed: 2.2s remaining: 0.3s
0.6001029721598573
{'max_depth': 4, 'n_estimators': 100}
[Parallel(n_jobs=2)]: Done 27 out of 27 | elapsed: 2.4s finished
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | LightGBM 建模应用详解

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/205
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
LightGBM 是微软开发的 boosting 集成模型,和 XGBoost 一样是对 GBDT 的优化和高效实现,原理有一些相似之处,但它很多方面比 XGBoost 有着更为优秀的表现。
本篇内容ShowMeAI展开给大家讲解 LightGBM 的工程应用方法,对于 LightGBM 原理知识感兴趣的同学,欢迎参考ShowMeAI的另外一篇文章 图解机器学习 | LightGBM 模型详解。
1.LightGBM 安装
LightGBM 作为常见的强大 Python 机器学习工具库,安装也比较简单。
1.1 Python 与 IDE 环境设置
python 环境与 IDE 设置可以参考ShowMeAI文章 图解 python | 安装与环境设置 进行设置。

1.2 工具库安装
(1) Linux/Mac 等系统
这些系统下的 XGBoost 安装,大家只要基于 pip 就可以轻松完成了,在命令行端输入命令如下命令即可等待安装完成。
pip install lightgbm
大家也可以选择国内的 pip 源,以获得更好的安装速度:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple lightgbm
(2) Windows 系统
对于 windows 系统而言,比较高效便捷的安装方式是:在网址www.lfd.uci.edu/~gohlke/pythonlibs/ 中去下载对应版本的的 LightGBM 安装包,再通过如下命令安装。
pip install lightgbm‑3.3.2‑cp310‑cp310‑win_amd64.whl
2.LightGBM 参数手册
在ShowMeAI的前一篇内容 XGBoost 工具库建模应用详解 中,我们讲解到了 Xgboost 的三类参数通用参数,学习目标参数,Booster 参数。而 LightGBM 可调参数更加丰富,包含核心参数,学习控制参数,IO 参数,目标参数,度量参数,网络参数,GPU 参数,模型参数,这里我常修改的便是核心参数,学习控制参数,度量参数等。下面我们对这些模型参数做展开讲解,更多的细节可以参考LightGBM 中文文档。
2.1 参数介绍
(1) 核心参数
-
config或者config_file:一个字符串,给出了配置文件的路径。默认为空字符串。 -
task:一个字符串,给出了要执行的任务。可以为:train或者training:表示是训练任务。默认为train。predict或者prediction或者test:表示是预测任务。convert_model:表示是模型转换任务。将模型文件转换成 if-else 格式。
-
application或者objective或者app:一个字符串,表示问题类型。可以为:regression或regression_l2或mean_squared_error或mse或l2_root或root_mean_squred_error或rmse:表示回归任务,但是使用 L2 损失函数。默认为regression。regression_l1或者mae或者mean_absolute_error:表示回归任务,但是使用 L1 损失函数。huber:表示回归任务,但是使用 huber 损失函数。fair:表示回归任务,但是使用 fair 损失函数。poisson:表示 Poisson 回归任务。quantile:表示 quantile 回归任务。quantile_l2:表示 quantile 回归任务,但是使用了 L2 损失函数。mape或者mean_absolute_precentage_error:表示回归任务,但是使用 MAPE 损失函数gamma:表示 gamma 回归任务。tweedie:表示 tweedie 回归任务。binary:表示二分类任务,使用对数损失函数作为目标函数。multiclass:表示多分类任务,使用 softmax 函数作为目标函数。必须设置num_class参数multiclassova或者multiclass_ova或者ova或者ovr:表示多分类任务,使用one-vs-all的二分类目标函数。必须设置num_class参数。xentropy或者cross_entropy:目标函数为交叉熵(同时具有可选择的线性权重)。要求标签是[0,1]之间的数值。xentlambda或者cross_entropy_lambda:替代了参数化的cross_entropy。要求标签是[0,1]之间的数值。lambdarank:表示排序任务。在lambdarank任务中,标签应该为整数类型,数值越大表示相关性越高。label_gain参数可以用于设置整数标签的增益(权重)。
-
boosting或者boost或者boosting_type:一个字符串,给出了基学习器模型算法。可以为:gbdt:表示传统的梯度提升决策树。默认值为gbdt。rf:表示随机森林。dart:表示带 dropout 的 gbdt。goss:表示 Gradient-based One-Side Sampling 的 gbdt。
-
data或者train或者train_data:一个字符串,给出了训练数据所在的文件的文件名。默认为空字符串。LightGBM 将使用它来训练模型。 -
valid或者test或者valid_data或者test_data:一个字符串,表示验证集所在的文件的文件名。默认为空字符串。LightGBM 将输出该数据集的度量。如果有多个验证集,则用逗号分隔。 -
num_iterations或者num_iteration或者num_tree或者num_trees或者num_round或者num_rounds或者num_boost_round一个整数,给出了boosting的迭代次数。默认为 100。- 对于 Python/R 包,该参数是被忽略的。对于 Python,使用
train()/cv()的输入参数num_boost_round来代替。 - 在内部,LightGBM 对于 multiclass 问题设置了
num_class*num_iterations棵树。
- 对于 Python/R 包,该参数是被忽略的。对于 Python,使用
-
learning_rate或者shrinkage_rate:个浮点数,给出了学习率。默认为 1。在 dart 中,它还会影响 dropped trees 的归一化权重。 -
num_leaves或者num_leaf:一个整数,给出了一棵树上的叶子数。默认为 31。 -
tree_learner或者tree:一个字符串,给出了 tree learner,主要用于并行学习。默认为serial。可以为:serial:单台机器的 tree learnerfeature:特征并行的 tree learnerdata:数据并行的 tree learnervoting:投票并行的 tree learner
-
num_threads或者num_thread或者nthread:一个整数,给出了 LightGBM 的线程数。默认为OpenMP_default。- 为了更快的速度,应该将它设置为真正的 CPU 内核数,而不是线程的数量(大多数 CPU 使用超线程来使每个 CPU 内核生成 2 个线程)。
- 当数据集较小的时候,不要将它设置的过大。
- 对于并行学习,不应该使用全部的 CPU 核心,因为这会使得网络性能不佳。
-
device:一个字符串,指定计算设备。默认为cpu。可以为gpu、cpu。- 建议使用较小的
max_bin来获得更快的计算速度。 - 为了加快学习速度,GPU 默认使用 32 位浮点数来求和。你可以设置
gpu_use_dp=True来启动 64 位浮点数,但是它会使得训练速度降低。
- 建议使用较小的
(2) 学习控制参数
max_depth:一个整数,限制了树模型的最大深度,默认值为-1。如果小于 0,则表示没有限制。min_data_in_leaf或者min_data_per_leaf或者min_data或者min_child_samples:一个整数,表示一个叶子节点上包含的最少样本数量。默认值为 20。min_sum_hessian_in_leaf或者min_sum_hessian_per_leaf或者min_sum_hessian或者min_hessian或者min_child_weight:一个浮点数,表示一个叶子节点上的最小 hessian 之和。(也就是叶节点样本权重之和的最小值)默认为 1e-3。feature_fraction或者sub_feature或者colsample_bytree:一个浮点数,取值范围为[0.0,1.0],默认值为 0。如果小于 1.0,则 LightGBM 会在每次迭代中随机选择部分特征。如 0.8 表示:在每棵树训练之前选择 80%的特征来训练。feature_fraction_seed:一个整数,表示feature_fraction的随机数种子,默认为 2。bagging_fraction或者sub_row或者subsample:一个浮点数,取值范围为[0.0,1.0],默认值为 0。如果小于 1.0,则 LightGBM 会在每次迭代中随机选择部分样本来训练(非重复采样)。如 0.8 表示:在每棵树训练之前选择 80%的样本(非重复采样)来训练。bagging_freq或者subsample_freq:一个整数,表示每bagging_freq次执行 bagging。如果该参数为 0,表示禁用 bagging。bagging_seed或者bagging_fraction_seed:一个整数,表示 bagging 的随机数种子,默认为 3。early_stopping_round或者early_stopping_rounds或者early_stopping:一个整数,默认为 0。如果一个验证集的度量在early_stopping_round循环中没有提升,则停止训练。如果为 0 则表示不开启早停。lambda_l1或者reg_alpha:一个浮点数,表示 L1 正则化系数。默认为 0。lambda_l2或者reg_lambda:一个浮点数,表示 L2 正则化系数。默认为 0。min_split_gain或者min_gain_to_split:一个浮点数,表示执行切分的最小增益,默认为 0。drop_rate:一个浮点数,取值范围为[0.0,1.0],表示 dropout 的比例,默认为 1。该参数仅在 dart 中使用。skip_drop:一个浮点数,取值范围为[0.0,1.0],表示跳过 dropout 的概率,默认为 5。该参数仅在 dart 中使用。max_drop:一个整数,表示一次迭代中删除树的最大数量,默认为 50。如果小于等于 0,则表示没有限制。该参数仅在 dart 中使用。uniform_drop:一个布尔值,表示是否想要均匀的删除树,默认值为 False。该参数仅在 dart 中使用。xgboost_dart_mode:一个布尔值,表示是否使用 xgboost dart 模式,默认值为 False。该参数仅在 dart 中使用。drop_seed:一个整数,表示 dropout 的随机数种子,默认值为 4。该参数仅在 dart 中使用。top_rate:一个浮点数,取值范围为[0.0,1.0],表示在 goss 中,大梯度数据的保留比例,默认值为 2。该参数仅在 goss 中使用。other_rate:一个浮点数,取值范围为[0.0,1.0],表示在 goss 中,小梯度数据的保留比例,默认值为 1。该参数仅在 goss 中使用。min_data_per_group:一个整数,表示每个分类组的最小数据量,默认值为 100。用于排序任务max_cat_threshold:一个整数,表示 category 特征的取值集合的最大大小。默认为 32。cat_smooth:一个浮点数,用于 category 特征的概率平滑。默认值为 10。它可以降低噪声在 category 特征中的影响,尤其是对于数据很少的类。cat_l2:一个浮点数,用于 category 切分中的 L2 正则化系数。默认为 10。top_k或者topk:一个整数,用于投票并行中。默认为 20。将它设置为更大的值可以获得更精确的结果,但是会降低训练速度。
(3) IO 参数
max_bin:一个整数,表示最大的桶的数量。默认值为 255。LightGBM 会根据它来自动压缩内存。如max_bin=255时,则 LightGBM 将使用 uint8 来表示特征的每一个值。min_data_in_bin:一个整数,表示每个桶的最小样本数。默认为 3。该方法可以避免出现一个桶只有一个样本的情况。data_random_seed:一个整数,表示并行学习数据分隔中的随机数种子。默认为 1 它不包括特征并行。output_model或者model_output或者model_out:一个字符串,表示训练中输出的模型被保存的文件的文件名。默认 txt。input_model或者model_input或者model_in:一个字符串,表示输入模型的文件的文件名。默认空字符串。对于 prediction 任务,该模型将用于预测数据,对于 train 任务,训练将从该模型继续output_result或者predict_result或者prediction_result:一个字符串,给出了 prediction 结果存放的文件名。默认为 txt。pre_partition或者is_pre_partition:一个布尔值,指示数据是否已经被划分。默认值为 False。如果为 true,则不同的机器使用不同的 partition 来训练。它用于并行学习(不包括特征并行)is_sparse或者is_enable_sparse或者enable_sparse:一个布尔值,表示是否开启稀疏优化,默认为 True。如果为 True 则启用稀疏优化。two_round或者two_round_loading或者use_two_round_loading:一个布尔值,指示是否启动两次加载。默认值为 False,表示只需要进行一次加载。默认情况下,LightGBM 会将数据文件映射到内存,然后从内存加载特征,这将提供更快的数据加载速度。但是当数据文件很大时,内存可能会被耗尽。如果数据文件太大,则将它设置为 Truesave_binary或者is_save_binary或者is_save_binary_file:一个布尔值,表示是否将数据集(包括验证集)保存到二进制文件中。默认值为 False。如果为 True,则可以加快数据的加载速度。verbosity或者verbose:一个整数,表示是否输出中间信息。默认值为 1。如果小于 0,则仅仅输出 critical 信息;如果等于 0,则还会输出 error,warning 信息;如果大于 0,则还会输出 info 信息。header或者has_header:一个布尔值,表示输入数据是否有头部。默认为 False。label或者label_column:一个字符串,表示标签列。默认为空字符串。你也可以指定一个整数,如 label=0 表示第 0 列是标签列。你也可以为列名添加前缀,如label=prefix:label_name。weight或者weight_column:一个字符串,表示样本权重列。默认为空字符串。你也可以指定一个整数,如 weight=0 表示第 0 列是权重列。注意:它是剔除了标签列之后的索引。假如标签列为 0,权重列为 1,则这里 weight=0。你也可以为列名添加前缀,如weight=prefix:weight_name。query或者query_column或者gourp或者group_column:一个字符串,query/groupID 列。默认为空字符串。你也可以指定一个整数,如 query=0 表示第 0 列是 query 列。注意:它是剔除了标签列之后的索引。假如标签列为 0,query 列为 1,则这里 query=0。你也可以为列名添加前缀,如query=prefix:query_name。ignore_column或者ignore_feature或者blacklist:一个字符串,表示训练中忽略的一些列,默认为空字符串。可以用数字做索引,如ignore_column=0,1,2表示第 0,1,2 列将被忽略。注意:它是剔除了标签列之后的索引。- 你也可以为列名添加前缀,如
ignore_column=prefix:ign_name1,ign_name2。 categorical_feature或者categorical_column或者cat_feature或者cat_column:一个字符串,指定 category 特征的列。默认为空字符串。可以用数字做索引,如categorical_feature=0,1,2表示第 0,1,2 列将作为 category 特征。注意:它是剔除了标签列之后的索引。你也可以为列名添加前缀,如categorical_feature=prefix:cat_name1,cat_name2在 categorycal 特征中,负的取值被视作缺失值。predict_raw_score或者raw_score或者is_predict_raw_score:一个布尔值,表示是否预测原始得分。默认为 False。如果为 True 则仅预测原始得分。该参数只用于 prediction 任务。predict_leaf_index或者leaf_index或者is_predict_leaf_index:一个布尔值,表示是否预测每个样本在每棵树上的叶节点编号。默认为 False。在预测时,每个样本都会被分配到每棵树的某个叶子节点上。该参数就是要输出这些叶子节点的编号。该参数只用于 prediction 任务。predict_contrib或者contrib或者is_predict_contrib:一个布尔值,表示是否输出每个特征对于每个样本的预测的贡献。默认为 False。输出的结果形状为[nsamples,nfeatures+1],之所以+1 是考虑到 bais 的贡献。所有的贡献加起来就是该样本的预测结果。该参数只用于 prediction 任务。bin_construct_sample_cnt或者subsample_for_bin:一个整数,表示用来构建直方图的样本的数量。默认为 200000。如果数据非常稀疏,则可以设置为一个更大的值,如果设置更大的值,则会提供更好的训练效果,但是会增加数据加载时间。num_iteration_predict:一个整数,表示在预测中使用多少棵子树。默认为-1。小于等于 0 表示使用模型的所有子树。该参数只用于 prediction 任务。pred_early_stop:一个布尔值,表示是否使用早停来加速预测。默认为 False。如果为 True,则可能影响精度。pred_early_stop_freq:一个整数,表示检查早停的频率。默认为 10pred_early_stop_margin:一个浮点数,表示早停的边际阈值。默认为 0use_missing:一个布尔值,表示是否使用缺失值功能。默认为 True 如果为 False 则禁用缺失值功能。zero_as_missing:一个布尔值,表示是否将所有的零(包括在 libsvm/sparse 矩阵中未显示的值)都视为缺失值。默认为 False。如果为 False,则将 nan 视作缺失值。如果为 True,则np.nan和零都将视作缺失值。init_score_file:一个字符串,表示训练时的初始化分数文件的路径。默认为空字符串,表示 train_data_file+”.init”(如果存在)valid_init_score_file:一个字符串,表示验证时的初始化分数文件的路径。默认为空字符串,表示 valid_data_file+”.init”(如果存在)。如果有多个(对应于多个验证集),则可以用逗号,来分隔。
(4) 目标参数
sigmoid:一个浮点数,用 sigmoid 函数的参数,默认为 0。它用于二分类任务和 lambdarank 任务。alpha:一个浮点数,用于 Huber 损失函数和 Quantileregression,默认值为 0。它用于 huber 回归任务和 Quantile 回归任务。fair_c:一个浮点数,用于 Fair 损失函数,默认值为 0。它用于 fair 回归任务。gaussian_eta:一个浮点数,用于控制高斯函数的宽度,默认值为 0。它用于 regression_l1 回归任务和 huber 回归任务。posson_max_delta_step:一个浮点数,用于 Poisson regression 的参数,默认值为 7。它用于 poisson 回归任务。scale_pos_weight:一个浮点数,用于调整正样本的权重,默认值为 0 它用于二分类任务。boost_from_average:一个布尔值,指示是否将初始得分调整为平均值(它可以使得收敛速度更快)。默认为 True。它用于回归任务。is_unbalance或者unbalanced_set:一个布尔值,指示训练数据是否均衡的。默认为 True。它用于二分类任务。max_position:一个整数,指示将在这个 NDCG 位置优化。默认为 20。它用于 lambdarank 任务。label_gain:一个浮点数序列,给出了每个标签的增益。默认值为 0,1,3,7,15,….它用于 lambdarank 任务。num_class或者num_classes:一个整数,指示了多分类任务中的类别数量。默认为 1 它用于多分类任务。reg_sqrt:一个布尔值,默认为 False。如果为 True,则拟合的结果为:\sqrt{label}。同时预测的结果被自动转换为:{pred}²。它用于回归任务。
(5) 度量参数
metric:一个字符串,指定了度量的指标,默认为:对于回归问题,使用 l2;对于二分类问题,使用binary_logloss;对于 lambdarank 问题,使用 ndcg。如果有多个度量指标,则用逗号,分隔。l1或者mean_absolute_error或者mae或者regression_l1:表示绝对值损失。l2或者mean_squared_error或者mse或者regression_l2或者regression:表示平方损失。l2_root或者root_mean_squared_error或者rmse:表示开方损失。quantile:表示 Quantile 回归中的损失。mape或者mean_absolute_percentage_error:表示 MAPE 损失。huber:表示 huber 损失。fair:表示 fair 损失。poisson:表示 poisson 回归的负对数似然。gamma:表示 gamma 回归的负对数似然。gamma_deviance:表示 gamma 回归的残差的方差。tweedie:表示 Tweedie 回归的负对数似然。ndcg:表示 NDCG。map或者mean_average_precision:表示平均的精度。auc:表示 AUC。binary_logloss或者binary:表示二类分类中的对数损失函数。binary_error:表示二类分类中的分类错误率。multi_logloss或者multiclass或者softmax或者‘multiclassova或者multiclass_ova,或者ova或者ovr`:表示多类分类中的对数损失函数。multi_error:表示多分类中的分类错误率。xentropy或者cross_entropy:表示交叉熵。xentlambda或者cross_entropy_lambda:表示 intensity 加权的交叉熵。kldiv或者kullback_leibler:表示 KL 散度。
metric_freq或者output_freq:一个正式,表示每隔多少次输出一次度量结果。默认为 1。train_metric或者training_metric或者is_training_metric:一个布尔值,默认为 False。如果为 True,则在训练时就输出度量结果。ndcg_at或者ndcg_eval_at或者eval_at:一个整数列表,指定了 NDCG 评估点的位置。默认为 1、2、3、4、5。
2.2 参数影响与调参建议
以下为总结的核心参数对模型的影响,及与之对应的调参建议。
(1) 对树生长控制

num_leaves:叶节点的数目。它是控制树模型复杂度的主要参数。- 如果是
level-wise,则该参数为 2 d e p t h 2^{depth} 2depth,其中 depth 为树的深度。但是当叶子数量相同时,leaf-wise 的树要远远深过 level-wise 树,非常容易导致过拟合。因此应该让 num_leaves 小于 2 d e p t h 2^{depth} 2depth。在 leaf-wise 树中,并不存在 depth 的概念。因为不存在一个从 leaves 到 depth 的合理映射。
- 如果是
min_data_in_leaf:每个叶节点的最少样本数量。- 它是处理
leaf-wise树的过拟合的重要参数。将它设为较大的值,可以避免生成一个过深的树。但是也可能导致欠拟合。
- 它是处理
max_depth:树的最大深度。该参数可以显式的限制树的深度。
(2) 更快的训练速度

- 通过设置
bagging_fraction和bagging_freq参数来使用 bagging 方法。 - 通过设置
feature_fraction参数来使用特征的子抽样。 - 使用较小的
max_bin。 - 使用
save_binary在未来的学习过程对数据加载进行加速。
(3) 更好的模型效果

- 使用较大的
max_bin(学习速度可能变慢)。 - 使用较小的
learning_rate和较大的num_iterations。 - 使用较大的
num_leaves(可能导致过拟合)。 - 使用更大的训练数据。
- 尝试
dart。
(4) 缓解过拟合问题

- 使用较小的
max_bin。 - 使用较小的
num_leaves。 - 使用
min_data_in_leaf和min_sum_hessian_in_leaf。 - 通过设置
bagging_fraction和bagging_freq来使用bagging。 - 通过设置
feature_fraction来使用特征子抽样。 - 使用更大的训练数据。
- 使用
lambda_l1、lambda_l2和min_gain_to_split来使用正则。 - 尝试
max_depth来避免生成过深的树。
3.LightGBM 内置建模方式
3.1 内置建模方式
LightGBM 内置了建模方式,有如下的数据格式与核心训练方法:
- 基于
lightgbm.Dataset格式的数据。 - 基于
lightgbm.train接口训练。
下面是官方的一个简单示例,演示了读取 libsvm 格式数据(成Dataset格式)并指定参数建模的过程。
# coding: utf-8
import json
import lightgbm as lgb
import pandas as pd
from sklearn.metrics import mean_squared_error
# 加载数据集合
print('加载数据...')
df_train = pd.read_csv('./data/regression.train.txt', header=None, sep='\t')
df_test = pd.read_csv('./data/regression.test.txt', header=None, sep='\t')
# 设定训练集和测试集
y_train = df_train[0].values
y_test = df_test[0].values
X_train = df_train.drop(0, axis=1).values
X_test = df_test.drop(0, axis=1).values
# 构建 lgb 中的 Dataset 格式
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
# 敲定好一组参数
params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'regression',
'metric': {'l2', 'auc'},
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': 0
}
print('开始训练...')
# 训练
gbm = lgb.train(params,
lgb_train,
num_boost_round=20,
valid_sets=lgb_eval,
early_stopping_rounds=5)
# 保存模型
print('保存模型...')
# 保存模型到文件中
gbm.save_model('model.txt')
print('开始预测...')
# 预测
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)
# 评估
print('预估结果的 rmse 为:')
print(mean_squared_error(y_test, y_pred) ** 0.5)

加载数据...
开始训练...
[1] valid_0's l2: 0.24288 valid_0's auc: 0.764496
Training until validation scores don't improve for 5 rounds.
[2] valid_0's l2: 0.239307 valid_0's auc: 0.766173
[3] valid_0's l2: 0.235559 valid_0's auc: 0.785547
[4] valid_0's l2: 0.230771 valid_0's auc: 0.797786
[5] valid_0's l2: 0.226297 valid_0's auc: 0.805155
[6] valid_0's l2: 0.223692 valid_0's auc: 0.800979
[7] valid_0's l2: 0.220941 valid_0's auc: 0.806566
[8] valid_0's l2: 0.217982 valid_0's auc: 0.808566
[9] valid_0's l2: 0.215351 valid_0's auc: 0.809041
[10] valid_0's l2: 0.213064 valid_0's auc: 0.805953
[11] valid_0's l2: 0.211053 valid_0's auc: 0.804631
[12] valid_0's l2: 0.209336 valid_0's auc: 0.802922
[13] valid_0's l2: 0.207492 valid_0's auc: 0.802011
[14] valid_0's l2: 0.206016 valid_0's auc: 0.80193
Early stopping, best iteration is:
[9] valid_0's l2: 0.215351 valid_0's auc: 0.809041
保存模型...
开始预测...
预估结果的 rmse 为:
0.4640593794679212
3.2 设置样本权重
LightGBM 的建模非常灵活,它可以支持我们对于每个样本设置不同的权重学习,设置的方式也非常简单,我们需要提供给模型一组权重数组数据,长度和样本数一致。
如下是一个典型的例子,其中binary.train和binary.test读取后加载为lightgbm.Dataset格式的输入,而在lightgbm.Dataset的构建参数中可以设置样本权重(这个例子中是 numpy array 的形态)。再基于lightgbm.train接口使用内置建模方式训练。
# coding: utf-8
import json
import lightgbm as lgb
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error
import warnings
warnings.filterwarnings("ignore")
# 加载数据集
print('加载数据...')
df_train = pd.read_csv('./data/binary.train', header=None, sep='\t')
df_test = pd.read_csv('./data/binary.test', header=None, sep='\t')
W_train = pd.read_csv('./data/binary.train.weight', header=None)[0]
W_test = pd.read_csv('./data/binary.test.weight', header=None)[0]
y_train = df_train[0].values
y_test = df_test[0].values
X_train = df_train.drop(0, axis=1).values
X_test = df_test.drop(0, axis=1).values
num_train, num_feature = X_train.shape
# 加载数据的同时加载权重
lgb_train = lgb.Dataset(X_train, y_train,
weight=W_train, free_raw_data=False)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train,
weight=W_test, free_raw_data=False)
# 设定参数
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'binary_logloss',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': 0
}
# 产出特征名称
feature_name = ['feature_' + str(col) for col in range(num_feature)]
print('开始训练...')
gbm = lgb.train(params,
lgb_train,
num_boost_round=10,
valid_sets=lgb_train, # 评估训练集
feature_name=feature_name,
categorical_feature=[21])
加载数据...
开始训练...
[1] training's binary_logloss: 0.68205
[2] training's binary_logloss: 0.673618
[3] training's binary_logloss: 0.665891
[4] training's binary_logloss: 0.656874
[5] training's binary_logloss: 0.648523
[6] training's binary_logloss: 0.641874
[7] training's binary_logloss: 0.636029
[8] training's binary_logloss: 0.629427
[9] training's binary_logloss: 0.623354
[10] training's binary_logloss: 0.617593
3.3 模型存储与加载
上述建模过程得到的模型对象,可以通过 save_model 成员函数进行保存。保存好的模型可以通过lgb.Booster加载回内存,并对测试集进行预测。
具体示例代码如下:
# 查看特征名称
print('完成 10 轮训练...')
print('第 7 个特征为:')
print(repr(lgb_train.feature_name[6]))
# 存储模型
gbm.save_model('./model/lgb_model.txt')
# 特征名称
print('特征名称:')
print(gbm.feature_name())
# 特征重要度
print('特征重要度:')
print(list(gbm.feature_importance()))
# 加载模型
print('加载模型用于预测')
bst = lgb.Booster(model_file='./model/lgb_model.txt')
# 预测
y_pred = bst.predict(X_test)
# 在测试集评估效果
print('在测试集上的 rmse 为:')
print(mean_squared_error(y_test, y_pred) ** 0.5)

完成 10 轮训练...
第 7 个特征为:
'feature_6'
特征名称:
['feature_0', 'feature_1', 'feature_2', 'feature_3', 'feature_4', 'feature_5', 'feature_6', 'feature_7', 'feature_8', 'feature_9', 'feature_10', 'feature_11', 'feature_12', 'feature_13', 'feature_14', 'feature_15', 'feature_16', 'feature_17', 'feature_18', 'feature_19', 'feature_20', 'feature_21', 'feature_22', 'feature_23', 'feature_24', 'feature_25', 'feature_26', 'feature_27']
特征重要度:
[8, 5, 1, 19, 7, 33, 2, 0, 2, 10, 5, 2, 0, 9, 3, 3, 0, 2, 2, 5, 1, 0, 36, 3, 33, 45, 29, 35]
加载模型用于预测
在测试集上的 rmse 为:
0.4629245607636925
3.4 继续训练
LightGBM 为 boosting 模型,每一轮训练会增加新的基学习器,LightGBM 还支持基于现有模型和参数继续训练,无需每次从头训练。
如下是典型的示例,我们加载已经训练 10 轮(即 10 颗树集成)的 lgb 模型,在此基础上继续训练(在参数层面做了一些改变,调整了学习率,增加了一些 bagging 等缓解过拟合的处理方法)
# 继续训练
# 从./model/model.txt 中加载模型初始化
gbm = lgb.train(params,
lgb_train,
num_boost_round=10,
init_model='./model/lgb_model.txt',
valid_sets=lgb_eval)
print('以旧模型为初始化,完成第 10-20 轮训练...')
# 在训练的过程中调整超参数
# 比如这里调整的是学习率
gbm = lgb.train(params,
lgb_train,
num_boost_round=10,
init_model=gbm,
learning_rates=lambda iter: 0.05 * (0.99 ** iter),
valid_sets=lgb_eval)
print('逐步调整学习率完成第 20-30 轮训练...')
# 调整其他超参数
gbm = lgb.train(params,
lgb_train,
num_boost_round=10,
init_model=gbm,
valid_sets=lgb_eval,
callbacks=[lgb.reset_parameter(bagging_fraction=[0.7] * 5 + [0.6] * 5)])
print('逐步调整 bagging 比率完成第 30-40 轮训练...')

[11] valid_0's binary_logloss: 0.616177
[12] valid_0's binary_logloss: 0.611792
[13] valid_0's binary_logloss: 0.607043
[14] valid_0's binary_logloss: 0.602314
[15] valid_0's binary_logloss: 0.598433
[16] valid_0's binary_logloss: 0.595238
[17] valid_0's binary_logloss: 0.592047
[18] valid_0's binary_logloss: 0.588673
[19] valid_0's binary_logloss: 0.586084
[20] valid_0's binary_logloss: 0.584033
以旧模型为初始化,完成第 10-20 轮训练...
[21] valid_0's binary_logloss: 0.616177
[22] valid_0's binary_logloss: 0.611834
[23] valid_0's binary_logloss: 0.607177
[24] valid_0's binary_logloss: 0.602577
[25] valid_0's binary_logloss: 0.59831
[26] valid_0's binary_logloss: 0.595259
[27] valid_0's binary_logloss: 0.592201
[28] valid_0's binary_logloss: 0.589017
[29] valid_0's binary_logloss: 0.586597
[30] valid_0's binary_logloss: 0.584454
逐步调整学习率完成第 20-30 轮训练...
[31] valid_0's binary_logloss: 0.616053
[32] valid_0's binary_logloss: 0.612291
[33] valid_0's binary_logloss: 0.60856
[34] valid_0's binary_logloss: 0.605387
[35] valid_0's binary_logloss: 0.601744
[36] valid_0's binary_logloss: 0.598556
[37] valid_0's binary_logloss: 0.595585
[38] valid_0's binary_logloss: 0.593228
[39] valid_0's binary_logloss: 0.59018
[40] valid_0's binary_logloss: 0.588391
逐步调整 bagging 比率完成第 30-40 轮训练...
3.5 自定义损失函数
LightGBM 支持在训练过程中,自定义损失函数和评估准则,其中损失函数的定义需要返回损失函数一阶和二阶导数的计算方法,评估准则部分需要对数据的 label 和预估值进行计算。其中损失函数用于训练过程中的树结构学习,而评估准则很多时候是用在验证集上进行效果评估。
# 自定义损失函数需要提供损失函数的一阶和二阶导数形式
def loglikelood(preds, train_data):
labels = train_data.get_label()
preds = 1. / (1. + np.exp(-preds))
grad = preds - labels
hess = preds * (1. - preds)
return grad, hess
# 自定义评估函数
def binary_error(preds, train_data):
labels = train_data.get_label()
return 'error', np.mean(labels != (preds > 0.5)), False
gbm = lgb.train(params,
lgb_train,
num_boost_round=10,
init_model=gbm,
fobj=loglikelood,
feval=binary_error,
valid_sets=lgb_eval)
print('用自定义的损失函数与评估标准完成第 40-50 轮...')

[41] valid_0's binary_logloss: 0.614429 valid_0's error: 0.268
[42] valid_0's binary_logloss: 0.610689 valid_0's error: 0.26
[43] valid_0's binary_logloss: 0.606267 valid_0's error: 0.264
[44] valid_0's binary_logloss: 0.601949 valid_0's error: 0.258
[45] valid_0's binary_logloss: 0.597271 valid_0's error: 0.266
[46] valid_0's binary_logloss: 0.593971 valid_0's error: 0.276
[47] valid_0's binary_logloss: 0.591427 valid_0's error: 0.278
[48] valid_0's binary_logloss: 0.588301 valid_0's error: 0.284
[49] valid_0's binary_logloss: 0.586562 valid_0's error: 0.288
[50] valid_0's binary_logloss: 0.584056 valid_0's error: 0.288
用自定义的损失函数与评估标准完成第 40-50 轮...
4.LightGBM 预估器形态接口
4.1 SKLearn 形态预估器接口
和 XGBoost 一样,LightGBM 也支持用 SKLearn 中统一的预估器形态接口进行建模,如下为典型的参考案例,对于读取为 Dataframe 格式的训练集和测试集,可以直接使用 LightGBM 初始化LGBMRegressor进行 fit 拟合训练。使用方法与接口,和 SKLearn 中其他预估器一致。
# coding: utf-8
import lightgbm as lgb
import pandas as pd
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import GridSearchCV
# 加载数据
print('加载数据...')
df_train = pd.read_csv('./data/regression.train.txt', header=None, sep='\t')
df_test = pd.read_csv('./data/regression.test.txt', header=None, sep='\t')
# 取出特征和标签
y_train = df_train[0].values
y_test = df_test[0].values
X_train = df_train.drop(0, axis=1).values
X_test = df_test.drop(0, axis=1).values
print('开始训练...')
# 初始化 LGBMRegressor
gbm = lgb.LGBMRegressor(objective='regression',
num_leaves=31,
learning_rate=0.05,
n_estimators=20)
# 使用 fit 函数拟合
gbm.fit(X_train, y_train,
eval_set=[(X_test, y_test)],
eval_metric='l1',
early_stopping_rounds=5)
# 预测
print('开始预测...')
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration_)
# 评估预测结果
print('预测结果的 rmse 是:')
print(mean_squared_error(y_test, y_pred) ** 0.5)

加载数据...
开始训练...
[1] valid_0's l1: 0.491735
Training until validation scores don't improve for 5 rounds.
[2] valid_0's l1: 0.486563
[3] valid_0's l1: 0.481489
[4] valid_0's l1: 0.476848
[5] valid_0's l1: 0.47305
[6] valid_0's l1: 0.469049
[7] valid_0's l1: 0.465556
[8] valid_0's l1: 0.462208
[9] valid_0's l1: 0.458676
[10] valid_0's l1: 0.454998
[11] valid_0's l1: 0.452047
[12] valid_0's l1: 0.449158
[13] valid_0's l1: 0.44608
[14] valid_0's l1: 0.443554
[15] valid_0's l1: 0.440643
[16] valid_0's l1: 0.437687
[17] valid_0's l1: 0.435454
[18] valid_0's l1: 0.433288
[19] valid_0's l1: 0.431297
[20] valid_0's l1: 0.428946
Did not meet early stopping. Best iteration is:
[20] valid_0's l1: 0.428946
开始预测...
预测结果的 rmse 是:
0.4441153344254208
4.2 网格搜索调参
上面提到 LightGBM 的预估器接口,整体使用方法和 SKLearn 中其他预估器一致,所以我们也可以使用 SKLearn 中的超参数调优方法来进行模型调优。
如下是一个典型的网格搜索交法调优超参数的代码示例,我们会给出候选参数列表字典,通过GridSearchCV进行交叉验证实验评估,选出 LightGBM 在候选参数中最优的超参数。
# 配合 scikit-learn 的网格搜索交叉验证选择最优超参数
estimator = lgb.LGBMRegressor(num_leaves=31)
param_grid = {
'learning_rate': [0.01, 0.1, 1],
'n_estimators': [20, 40]
}
gbm = GridSearchCV(estimator, param_grid)
gbm.fit(X_train, y_train)
print('用网格搜索找到的最优超参数为:')
print(gbm.best_params_)

用网格搜索找到的最优超参数为:
{'learning_rate': 0.1, 'n_estimators': 40}
4.3 绘图解释
LightGBM 支持对模型训练进行可视化呈现与解释,包括对于训练过程中的损失函数取值与评估准则结果的可视化、训练完成后特征重要度的排序与可视化、基学习器(比如决策树)的可视化。
以下为参考代码:
# coding: utf-8
import lightgbm as lgb
import pandas as pd
try:
import matplotlib.pyplot as plt
except ImportError:
raise ImportError('You need to install matplotlib for plotting.')
# 加载数据集
print('加载数据...')
df_train = pd.read_csv('./data/regression.train.txt', header=None, sep='\t')
df_test = pd.read_csv('./data/regression.test.txt', header=None, sep='\t')
# 取出特征和标签
y_train = df_train[0].values
y_test = df_test[0].values
X_train = df_train.drop(0, axis=1).values
X_test = df_test.drop(0, axis=1).values
# 构建 lgb 中的 Dataset 数据格式
lgb_train = lgb.Dataset(X_train, y_train)
lgb_test = lgb.Dataset(X_test, y_test, reference=lgb_train)
# 设定参数
params = {
'num_leaves': 5,
'metric': ('l1', 'l2'),
'verbose': 0
}
evals_result = {} # to record eval results for plotting
print('开始训练...')
# 训练
gbm = lgb.train(params,
lgb_train,
num_boost_round=100,
valid_sets=[lgb_train, lgb_test],
feature_name=['f' + str(i + 1) for i in range(28)],
categorical_feature=[21],
evals_result=evals_result,
verbose_eval=10)
print('在训练过程中绘图...')
ax = lgb.plot_metric(evals_result, metric='l1')
plt.show()
print('画出特征重要度...')
ax = lgb.plot_importance(gbm, max_num_features=10)
plt.show()
print('画出第 84 颗树...')
ax = lgb.plot_tree(gbm, tree_index=83, figsize=(20, 8), show_info=['split_gain'])
plt.show()
#print('用 graphviz 画出第 84 颗树...')
#graph = lgb.create_tree_digraph(gbm, tree_index=83, name='Tree84')
#graph.render(view=True)

加载数据...
开始训练...
[10] training's l2: 0.217995 training's l1: 0.457448 valid_1's l2: 0.21641 valid_1's l1: 0.456464
[20] training's l2: 0.205099 training's l1: 0.436869 valid_1's l2: 0.201616 valid_1's l1: 0.434057
[30] training's l2: 0.197421 training's l1: 0.421302 valid_1's l2: 0.192514 valid_1's l1: 0.417019
[40] training's l2: 0.192856 training's l1: 0.411107 valid_1's l2: 0.187258 valid_1's l1: 0.406303
[50] training's l2: 0.189593 training's l1: 0.403695 valid_1's l2: 0.183688 valid_1's l1: 0.398997
[60] training's l2: 0.187043 training's l1: 0.398704 valid_1's l2: 0.181009 valid_1's l1: 0.393977
[70] training's l2: 0.184982 training's l1: 0.394876 valid_1's l2: 0.178803 valid_1's l1: 0.389805
[80] training's l2: 0.1828 training's l1: 0.391147 valid_1's l2: 0.176799 valid_1's l1: 0.386476
[90] training's l2: 0.180817 training's l1: 0.388101 valid_1's l2: 0.175775 valid_1's l1: 0.384404
[100] training's l2: 0.179171 training's l1: 0.385174 valid_1's l2: 0.175321 valid_1's l1: 0.382929


参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | 综合项目-电商销量预估

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/206
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
1.案例介绍
在本篇内容中,ShowMeAI将基于 Kaggle 数据科学竞赛平台的 Rossmann store sales 大数据竞赛项目,给大家梳理和总结,基于 Python 解决电商建模的全过程:包括数据探索分析、数据预处理与特征工程、建模与调优。
本篇对应的结构和内容如下。
- 第①节:介绍本篇中我们解决方案所用到的 Python 工具库。
- 第②节:介绍 Rossmann store sales 项目基本情况,包括业务背景、数据形态、项目目标。
- 第③节:介绍结合业务和数据做 EDA,即探索性数据分析的过程。
- 第④节:介绍应用 Python 机器学习工具库 SKLearn/XGBoost/LightGBM 进行建模和调优的过程。
2.工具库介绍
(1) Numpy
Numpy(Numerical Python)是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。Numpy 的主要特征如下:

- 一个强大的 N 维数组对象 ndarray
- 广播功能函数
- 整合 C/C++/Fortran 代码的工具
- 线性代数、傅里叶变换、随机数生成等功能
想对 Numpy 有详细了解的宝宝,欢迎查看ShowMeAI的 数据分析系列教程 中的 Numpy 部分。大家也可以查看ShowMeAI总结的 Numpy 速查表 数据科学工具速查 | Numpy 使用指南 做一个快速了解。
(2) Pandas
Pandas 是一个强大的序列数据处理工具包,项目开发之初是为了分析公司的财务数据以及金融数据。如今 Pandas 广泛地应用在了其他领域的数据分析中。它提供了大量能使我们快速便捷地处理数据的函数和方法,非常强大。

想对 Pandas 有详细了解的宝宝,欢迎查看ShowMeAI的 数据分析系列教程 中的 Pandas 部分。大家也可以查看ShowMeAI总结的 Pandas 速查表 数据科学工具速查 | Pandas 使用指南 做一个快速了解。
(3) Matplotlib
Matplotlib 是 Python 最强大的绘图工具之一,其主要用于绘制 2D 图形或 3D 图形的 2D 示意图。其在数据分析领域它有很大的地位,因为可视化可以帮助我们更清晰直观地了解数据分布特性。

想学习 Matplotlib 工具库的宝宝,也可以查看ShowMeAI总结的 Matplotlib 速查表 数据科学工具速查 | Matplotlib 使用指南 做一个快速了解。
(4) Seaborn
Seaborn 是基于 Python 且非常受欢迎的图形可视化库,在 Matplotlib 的基础上,进行了更高级的封装,使得作图更加方便快捷。即便是没有什么基础的人,也能通过极简的代码,做出具有分析价值而又十分美观的图形。

想对 Seaborn 有详细了解的宝宝,欢迎查看ShowMeAI的 数据分析系列教程 中的 Seaborn 部分。
大家也可以查看ShowMeAI总结的 Seaborn 速查表 数据科学工具速查 | Seaborn 使用指南 做一个快速了解。
(5) Scikit-Learn
Python 机器学习工具库 Scikit-Learn,构建在 Numpy,SciPy,Pandas 和 Matplotlib 之上,也是最常用的 Python 机器学习工具库之一,里面的 API 的设计非常好,所有对象的接口简单,很适合新手上路。覆盖的模型非常多,适用场景广泛。

想对 Scikit-Learn 有详细了解的宝宝,欢迎查看ShowMeAI的机器学习实战教程中的SKLearn 入门 和 SKLearn 指南 部分。也可以查看ShowMeAI总结的 Scikit-Learn 速查表 AI 建模工具速查 | Scikit-Learn 使用指南 做一个快速了解。
(6) XGBoost
XGBoost 是 eXtreme Gradient Boosting 的缩写称呼,它是一个非常强大的 Boosting 算法工具包,优秀的性能(效果与速度)让其在很长一段时间内霸屏数据科学比赛解决方案榜首,现在很多大厂的机器学习方案依旧会首选这个模型。XGBoost 在并行计算效率、缺失值处理、控制过拟合、预测泛化能力上都变现非常优秀。

想对 XGBoost 有详细了解的宝宝,欢迎查看ShowMeAI的文章 图解机器学习 | XGBoost 模型详解 理解其原理,以及文章 XGBoost 工具库建模应用详解 了解详细用法。
(7) LightGBM
LightGBM 是微软开发的 boosting 集成模型,和 XGBoost 一样是对 GBDT 的优化和高效实现,原理有一些相似之处,但它很多方面比 XGBoost 有着更为优秀的表现。

想对 LightGBM 有详细了解的宝宝,欢迎查看ShowMeAI的文章 图解机器学习 | LightGBM 模型详解 理解其原理,以及文章 LightGBM 工具库建模应用详解 了解详细用法。
3.项目概况介绍
本项目源于 Kaggle 平台的大数据机器学习比赛 Rossmann Store Sales,下面对其展开做介绍。
3.1 背景介绍
Rossmann 成立于 1972 年,是德国最大的日化用品超市,在 7 个欧洲国家有 3000 多家商店。商店不定时会举办短期的促销活动以及连续的促销活动以此来提高销售额。除此之外,商店的销售还受到许多因素的影响,包括促销、竞争、学校和国家假日、季节性和周期性。

3.2 数据介绍
数据以 1115 家 Rossmann 连锁商店为研究对象,从 2013 年 1 月 1 日到 2015 年 7 月共计录 1017209 条销售数据(27 个特征)。
数据集一共涵盖了四个文件:
train.csv:含有销量的历史数据test.csv:未含销量的历史数据sample_submission.csv:以正确格式提交的示例文件store.csv:关于每个商店的一些补充信息
其中,train.csv中的数据中一共含有 9 列信息:
store:为对应店铺的 id 序号DayOfWeek:代表着每周开店的天数Data:是对应销售额 Sales 产生的日期Sales:就是销售额的历史数据Customers:为进店的客人数量Open:则表示这个店铺是否开门与否Promo:表示商店是否在当天有促销活动StateHoliday:与 SchoolHoliday 分别表示了是否是国定假日或是学校假日。
(1) train.csv
我们在 Kaggle 的 data 页面下部的数据概览可以大致查看每个数据的分布情况和部分数据样例如下:

(2) test.csv
test.csv中的数据列几乎和train.csv一样,但缺少了 Sales(也就是销售数据)以及 Customers(用户流量)这两列。而我们的最终目标就是利用test.csv以及store.csv中的补充信息预测出test.csv中缺失的 Sales 数据。
test.csv的数据分布情况,可以看到和上面相比缺少了 Sales 以及与 Sales 有强关联的 Customer 数据。
数据分布和部分示例数据如下:

(3) sample_submission.csv
结果文件sample_submission.csv中仅有 id 与 Sales 这两列,这个文件是我们将我们的预测答案提交至 Kaggle 的判题器上的标准格式模板。
在 Python 中我们只需要打开此文件,并将预测数据按照顺序填入 Sales 这一列后,使用Dataframe.to_csv(‘sample_submission.csv‘)后就可以将带有预测数据的sample_submission.csv保存至本地并准备后续上传。

(4) store.csv
大家可以看到,train.csv与test.csv中有对应的店铺 id,这些店铺 id 的详细情况就对应在store.csv中,其中记录了一些店铺的地理位置信息以及营促销信息。
store.csv的数据分布情况,可以注意到这里有很多离散的类别标签。
数据分布和部分示例数据如下:

其中:
Store:对应表示了店铺的编号。StoreType:店铺的种类,一共有 a、b、c、d 四种不同种类的店铺。大家可以把它想象成快闪店,普通营业店,旗舰店,或 mini 店这样我们生活中的类型。Assortment:用 a、b、c 三种分类描述店铺内售卖产品的组合级别。例如旗舰店和 mini 店中组合的产品肯定是有很大不同的。Competition Distance、Competition Open Since Year、Competition Open Since Month:分别表示最近的竞争对手的店铺距离,开店时间(以年计算),开店时间(以月计算)。Promo2:描述该店铺是否有长期的促销活动。Promo2 Since Year于Promo2 Since Week:分别表示商店开始参与促销的年份和日历周。Promo Interval:描述promo2开始的连续间隔,以促销重新开始的月份命名。
3.3 项目目标
在了解了这些数据后我们就需要明确一下我们的项目目的,在 Rossmanns 销售预测中,我们需要利用历史数据,也就是train.csv中的数据进行监督学习。训练出的模型利用通 test.csv 中的数据进行模型推断(预测),将预测出的数据以sample_submission.csv的格式提交至 Kaggle 进行评分。在这过程中还可以结合store.csv中的补充信息加强我们模型获得数据的能力。
3.4 评估准则
模型所采纳的评估指标为 Kaggle 在竞赛中所推荐的 Root Mean Square Percentage Error (RMSPE)指标。
R M S P E = 1 n ∑ i = 1 n ( y i − y ^ i y i ) 2 = 1 n ∑ i = 1 n ( y ^ i y i − 1 ) 2 RMSPE = \sqrt{\frac{1}{n}\sum\limits_{i=1}^n\left(\frac{y_i-\hat{y}i}{y_i}\right)²} = \sqrt{\frac{1}{n}\sum\limits^n\left(\frac{\hat{y}_i}{{y}_i}-1\right)²} RMSPE=n1i=1∑n(yiyi−y^i)2 =n1i=1∑n(yiy^i−1)2
其中:
- y i y_i yi 代表门店当天的真实销售额。
- y ^ i \hat{y}_i y^i 代表相对应的预测销售额。
- n n n 代表样本的数量。
如果有任何一天的销售额为 0,那么将会被忽略。最后计算得到的这个 RMSPE 值越小代表误差就越小,相应就会获得更高的评分。
4.EDA 探索性数据分析
本案例涉及到的数据规模比较大,我们无法直接通过肉眼查看数据特性,但是对于数据分布特性的理解,可以帮助我们在后续的挖掘与建模中取得更好的效果。我们在这里会借助之前介绍过的 Pandas、Matplotlib、Seaborn 等工具来对数据进行分析和可视化理解。
这个部分我们使用的 IDE 为 Jupyter Notebook,比较方便进行交互式绘图探索数据特性。
4.1 折线图
我们使用了matplotlib.pyplot绘制了序号为 1 的店铺从 2013 年 1 月到 2015 年月的销售数据的曲线图。
train.loc[train['Store']==1,['Date','Sales']].plot(x='Date',y='Sales',title='The Sales Data In Store 1',figsize=(16,4))
代码解释:
-
我们使用 Pandas 读取
train.csv到train变量中。 -
再利用
.loc()这个函数对 train 这个 Dataframe 进行了数据筛选。- 筛选出 Store 编号为 1 的所有数据中的 Date 与 Sales 这两列数据,也就是下中对应的 x 轴与 y 轴。
-
然后利用 Pandas 内置的
plot方法进行绘图。在plot()中我们给图片设定了一系列的客制化参数:- x 轴对应的数据为
Date列 - y 轴的数据为
Sales列 - 设定了图片的标题为
The Sales Data In Store 1 - 图片的尺寸为(16, 4)
- x 轴对应的数据为
-
Matplotlib 中的
figsize参数可以约束图片的长宽以及大小,我们这里设置为(16, 4),意味可视化图片的像素大小为 1600*400 像素。

编号为 1 号的店铺从 2013 年 1 月至 2015 年 8 月的销售量曲线图
如果我们想查看一定时间范围内的销售额数据,可以调整loc函数内容对 x 轴进行范围选取。
train.loc[train['Store']==1,['Date','Sales']].plot(x='Date',y='Sale s',title='Store1',figsize=(8,2),xlim=['2014-6-1','2014-7-31'])
上述代码增加了 xlim 参数,达到了在 x 轴上对时间线进行截取的目的。

编号为 1 号的店铺从 2014 年 6 月 1 日至 2014 年 7 月 31 日的销售量曲线图
4.2 单变量分布图
下面我们对单维度特征进行数据分析,Seaborn 提供了distplot()这个方便就可以绘制数据分布的 api。
sns.distplot(train.loc[train['Store']==1,['Date','Sales']]['Sales'],bins=10, rug=True)
得到的结果如下,即一号店铺这么多年来全部销售状态的分布情况。

编号为 1 号的店铺从 2013 年 1 月至 2015 年 8 月的销售量数据分布
通过数据分布图就可以看出数据主要分布在 4000-6000 这一销售额中,数据的分布范围则从 2000-10000,整体基本符合高斯分布(正态分布)。
因为销售额是我们的预测目标,提前明确预测数据的分布非常有用,在训练集和测试集的分布明显有区别时,我们在预测的数据上进行一定的操作(例如乘以一个固定系数进行调整等),有时可以大幅改善预测的效果,在后续的建模部分我们也会采用这个策略。
同样的单变量分布分析,可以应用在其他的特征变量上。
4.3 二元变量联合分布图
除了对单变量进行分布分析,我们还可以通过对二元变量进行交叉联合分布分析获得更多的关联信息。在 Seaborn 中的jointplot()函数可以帮助我们很好的分析两个变量之间的关系。
sns.jointplot(x=train["Sales"], y=train["Customers"], kind="hex")
下图中显示了销售额(x 轴)与客户流量(y 轴)之间的关系,并且在各自的轴上显示了对于轴的数据分布状态。

2013 年 1 月至 2015 年 8 月的销售量与客户流量的关系
二元变量关联分析绘图,可以帮我们直观地观察出两列数据之间的相关性,在上图中我们就可以很轻易的观测出客户流量和销售流量是有一定线性关系的。
在jointplot()中还可以给其传递不同的 kind 参数改变图像的风格,例如下图中我们将 kind 的参数从hex改为reg,下图风格就从六边形风格变成了如下风格,并增加了两个列数据组成的回归线以表示数据的基本趋势。

2013 年 1 月至 2015 年 8 月的销售量与客户流量的关系
上面绘制的图中,还呈现了一些参数指标信息如kendaltau=0.76以及p=2.8e-19这些信息。这可以结合scipy以及函数中的stat_func=参数进行计算指标的传递。
这里给出范例代码:
sns.jointplot(x=train["Sales"], y=train["Customers"], kind="hex", stat_func=stats.kendalltau, kind="reg")
4.4 箱线图
其他常用的分析工具还包括箱线图(Box-plot,又称为盒须图、盒式图或箱形图),它可以清晰呈现数据分布的统计特性,包括一组数据的最大值、最小值、中位数及上下四分位数。
下面以销售数据为例,讲解使用 Seaborn 中的boxplot()函数对销售数据进行分析的过程。
sns.boxplot(train.Sales, palette="Set3")
其中,train.Sales是另外一种读取 Pandas 某列数据的方式,当然你也可以使用x=train ["Sales"]来达到同样的效果。

销售额数据的箱线图
我们想将销售数据分成 4 个箱线图(不同店铺类型),每个箱线图表示一周店铺类型的销售额。先将 store 中的 storeTpye 店铺类型数据放入train的数据中,即如下做合并。
train = pd.merge(train, store, on='Store')
上述代码中merge将两个 Dataframe 数据以某一列为索引进行合并,合并的依据为参数on。在这里将参数on设置为了Store就意味着以 Store`为索引进行合并。
接下来我们可以使用boxplot()函数进行两列数据的结合分析,其中 x 轴应该是店铺类别数据而 y 应该是销售额的箱线图数据。
sns.boxplot(x="StoreType", y="Sales", data=train, palette="Set3")
palette参数可以调节箱线图的配色状态,这里用的是Set3(色彩可以根据自己的个人喜好或展示要求进行修改)。

不同店铺类型下的销售额箱线图情况
可以看出不同店铺类型特别是店铺类型 b 的店铺销售额要明显高出其他店铺。a、c、d 三种店铺的无论是中位数还是四分位数数据都基本相同。
Seaborn 中的函数violinplot()也提供了和箱线图功能类似的提琴图功能,下面以代码举例。
sns.violinplot(x="StoreType", y="Sales", data=train, palette="Set3")

不同店铺类型下的销售额提琴图情况
在提琴图中将箱线图里中位数,四分位的位置标线等数据变为了数据的整体分布情况,在这里我们看见 a、d、c 三类店铺都有很多数据非常接近于 0,这可能是店铺在那一天关门等情况导致的。
4.5 热力图
如果我们希望更清晰地探索多变量之间的两两关联度,热力图是一个很不错的选择。作为一种密度图,热力图一般使用具备显著颜色差异的方式来呈现数据效果,热力图中亮色一般代表事件发生频率较高或事物分布密度较大,暗色则反之。
在 Seaborn 中要绘制热力图,我们会应用到 Pandas 中的corr()函数,该函数计算每列数据之间的相关性。这里的相关性为 Pearson 相关系数,可以由以下公式得到。
ρ X , Y = cov ( X , Y ) σ X σ Y = E ( ( X − μ X ) ( Y − μ Y ) ) σ X σ Y \rho_{X, Y}=\frac{\operatorname{cov}(X, Y)}{\sigma_{X} \sigma_{Y}}=\frac{E\left(\left(X-\mu_{X}\right)\left(Y-\mu_{Y}\right)\right)}{\sigma_{X} \sigma_{Y}} ρX,Y=σXσYcov(X,Y)=σXσYE((X−μX)(Y−μY))
计算相关性矩阵的代码如下所示:
train_corr = train.corr()
接下来就可以直接利用 Seaborn 的heatmap()函数进行热力图的绘制。
sns.heatmap(train.corr(), annot=True, vmin=-0.1,vmax=0.1,center=0)
上述代码中:
- 参数
annot=True是在热力图上显示相关系数矩阵的数值 vim与 vmax`规定了右侧色卡的显示范围,这里我们设置为了从-0.1 至-0.1 的范围center=0表示我们将中心值设置为 0

各列的相关性热力图
上图显示不少参数之间都具有一定的正相关性或者负相关性,意味着这些数据之间有一定的关联度,也就是说我们可以将这些数据使用机器学习模型进行分类或回归。
5.模型的训练与评估
本节我们先带大家回顾一些机器学习基础知识,再基于不同的机器学习工具库和模型进行建模。
5.1 过拟合和欠拟合
过拟合是指模型可以很好的拟合训练样本,但对新数据的预测准确性很差,泛化能力弱。欠拟合是指模型不能很好的拟合训练样本,且对新数据的预测性也不好。

过拟合欠拟合示意图
更详细的讲解大家可以参考ShowMeAI的文章图解机器学习 | 机器学习基础知识
5.2 评估准则
在 Scikit-Learn,XGBoost 或是 LightGBM 中,我们往往使用各种评价标准来表达模型的性能。最常用的往往有以下评估准则,对应二分类,多分类,回归等等不同的问题。
rmse:均方根误差mae:平均绝对误差logloss:负对数似然函数值error:二分类错误率merror:多分类错误率mlogloss:多分类 logloss 损失函数auc:曲线下面积
当然也可以通过定义自己的 loss function 进行损失函数定义。
5.3 交叉验证
留出法的数据划分,可能会带来偏差。在机器学习中,另外一种比较常见的评估方法是交叉验证法——K 折交叉验证对 K 个不同分组训练的结果进行平均来减少方差。
因此模型的性能对数据的划分就不那么敏感,对数据的使用也会更充分,模型评估结果更加稳定,可以很好地避免上述问题。
更详细的讲解大家可以参考ShowMeAI的文章 图解机器学习 | 机器学习基础知识。

5.4 建模工具库与模型选择
本项目很显然是一个回归类建模问题,我们可以先从回归树( 图解机器学习 | 回归树模型详解),后可以尝试集成模型,例如随机森林( 图解机器学习 | 随机森林分类模型详解)、XGBoost( 图解机器学习 | XGBoost 模型详解)、LightGBM( 图解机器学习 | LightGBM 模型详解)。
考虑到参加比赛的同学的整体算力资源可能参差不齐所以本文将主要讲解如何利用 LightGBM 进行模型的训练。本文只提供一些核心代码演示,更加细节的文档可以参考 LightGBM 中文文档。
大家通过ShowMeAI前面的工具库详解 LightGBM 建模应用详解 知道,如果用 LightGBM 进行训练学习,训练代码非常简单:
model = lgb.train(params=lgb_parameter, feval=rmsle, train_set=train_data, num_boost_round=15000, valid_sets=watchlist, early_stopping_rounds=1000, verbose_eval=1000)
代码解释:
params:定义 lgb 算法的一些参数设置,如评价标准,学习率,任务类型等。feval:可以让 lgb 使用自定义的损失函数。train_set:训练集的输入。num_boost_round:最大的训练次数。valid_sets:测试集的输入。early_stopping_rounds:当模型评分在 n 个回合后还没有提高时就结束模型将最佳的点的模型保存。verbose_eval:表示每多少论返回一次训练的评价信息,这里定义了每 1000 轮保存一次。
5.5 数据预处理
为了建模有好的效果,我们很少直接用原始数据,一般会先对数据进行预处理。
我们先用pd.merge方法将store与train数据合并,得到以下 DataFrame 数据:

合并后数据的前 5 行概览
首先我们看到有一些类别型字段,比如SotreType、Assortment以及StateHoliday,是以 a、b、c、d 这样的数值形式保存的,常用的数据预处理或者特征工程,会将其进行编码。我们这里用mapping函数将其编码变换为 0123 这样的数字。
mappings = {'0':0, 'a':1, 'b':2, 'c':3, 'd':4}
data['StoreType'] = data.StoreType.map(mappings)
data['Assortment']= data.Assortment.map(mappings)
data['StateHoliday']= data.StateHoliday.map(mappings)
再注意到时间型的字段 date,是以YYYY-MM-DD这样的日期时间戳的形式记录的。我们最常做的操作,是将时间戳拆分开来,比如年月日可以拆分成Year、Month、Day这样的形式会更有利于我们的模型学习到有效的信息。
data['Year'] = data.Date.dt.year
data['Month'] = data.Date.dt.month
data['Day'] = data.Date.dt.day
data['DayOfWeek'] = data.Date.dt.dayofweek
data['WeekOfYear'] = data.Date.dt.weekofyear
上述代码还抽取了「一年内的第几周」和「一周中的第几天」两列额外信息。这些都可以在 Pandas 中的 dt 方法中找到。
再看CompetitionOpenSince和Promo2Since两个字段,这两列数据表示促销开始的时间,这样的数据是固定的,但是开始的时间到当前时间点的数据是很有价值帮助我们预测销售额。所以这里需要一定变化,我们将促销开始的时间减去当前 data 的时间即可。
data['CompetitionOpen']=12*(data.Year-data.CompetitionOpenSinceYear)+(data.Month - data.CompetitionOpenSinceMonth)
data['PromoOpen'] = 12 *(data.Year-data.Promo2SinceYear)+ (data.WeekOfYear - data.Promo2SinceWeek) / 4.0
data['CompetitionOpen'] = data.CompetitionOpen.apply(lambda x: x if x > 0 else 0)
data['PromoOpen'] = data.PromoOpen.apply(lambda x: x if x > 0 else 0)
CompetitionOpen和PromoOpen是两列计算距离促销,或竞争对手开始时间长短的字段,用于表达促销或竞争对手对于销售额的影响。我们先做异常值处理(滤除所有负值)。
在数据中还有一列PromoInterval以列出月信息的形式储存正在促销的月份,我们想将其转化为以月份信息为准,如这个时间点的月份在这个PromoInterval中则这一店的这一时间点在促销中。
data.loc[data.PromoInterval == 0, 'PromoInterval'] = ''
data['IsPromoMonth'] = 0
for interval in data.PromoInterval.unique():
if interval != '':
for month in interval.split(','):
data.loc[(data.monthStr == month) & (data.PromoInterval == interval), 'IsPromoMonth'] = 1
和店铺类型中的标签转换类似,我们将月份从数字转化为 str 类型以和PromoInterval进行配对用于断当前时间是否是销售时间,并且新建一个数据列IsPromoMonth进行储存当前时间是否为促销时间。
当然,如果我们深度思考,还可以有很多事情做。例如:
- 在原始数据中有很多店铺没有开门的情况,既
Open的值为 0。而预测数值的情况下我们默认店铺不会存在关门的情况,所以可以在数据筛选的过程中清理 Open 为 0 的情况。 - 在数据中还一部分
Sales数值小于零的情况,这里猜测是有一些意外情况导致的记账信息错误,所以可以在数据清洗中时也直接过滤这一部分数据。
5.6 模型参数
很多机器学习有很多超参数可以调整,以这里的 LightGBM 为例,下面为选出的系列参数。关于 LightGBM 的参数和调参方法可以参考 LightGBM 建模应用详解。
params ={
'boosting_type': 'gbdt',
'objective': 'regression',
'metric':'rmse',
'eval_metric':'rmse',
'learning_rate': 0.03,
'num_leaves': 400,
#'max_depth' : 10,
'subsample': 0.8,
"colsample_bytree": 0.7,
'seed':3,
}
上述参数包含两类:
主要参数:确定任务和模型的时候就会确定下来。
boosting_type:是模型的类型(常选择 gbdt 或 dart)。objective:决定了模型是完成一个分类任务还是回归任务。metric:为模型训练时的评估准则。eval_metric:为模型评价时的评估准则。
模型可调细节参数:对模型的构建和效果有影响的参数。
learning_rate:表示每次模型学习时的学习率。num_leaves:是最多叶子数。leaf-wise 的 LightGBM 算法主要由叶子数来控制生长和过拟合,如果树深为max_depth,它的值的设置应该小于 2^(max_depth),否则可能会导致过拟合。is_unbalance:设置可以应对类别非均衡数据集。min_data_in_leaf:叶子节点最少样本数,调大它的值可以防止过拟合,它的值通常设置的比较大。
# coding: utf-8
import lightgbm as lgb
import pandas as pd
from sklearn.metrics import mean_squared_error
# 设定训练集和测试集
y_train = train['Sales'].values
X_train = train.drop('Sales', axis=1).values
# 构建 lgb 中的 Dataset 格式
lgb_train = lgb.Dataset(X_train, y_train)
# 敲定好一组参数
params = {
'boosting_type': 'gbdt',
'objective': 'regression',
'metric':'rmse',
'eval_metric':'rmse',
'learning_rate': 0.03,
'num_leaves': 400,
#'max_depth' : 10,
'subsample': 0.8,
"colsample_bytree": 0.7,
'seed':3,
}
print('开始训练...')
# 训练
gbm = lgb.train(params,
lgb_train,
num_boost_round=200)
# 保存模型
print('保存模型...')
# 保存模型到文件中
gbm.save_model('model.txt')
参考资料
- 数据分析系列教程
- 机器学习算法系列教程
- Kaggle Rossmann Store Sales 机器学习比赛
- 数据科学工具速查 | Numpy 使用指南
- 数据科学工具速查 | Numpy 使用指南
- 数据科学工具速查 | Pandas 使用指南
- 数据科学工具速查 | Matplotlib 使用指南
- 数据科学工具速查 | Seaborn 使用指南
- AI 建模工具速查 | Scikit-Learn 使用指南
- 图解机器学习 | XGBoost 模型详解
- 图解机器学习 | LightGBM 模型详解
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | 综合项目-电商销量预估进阶方案

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/207
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
同样还是 Rossmann 这个场景问题,ShowMeAI在上一篇 机器学习实战 | Python 机器学习综合项目-电商销量预估 里给大家讲解了基本的数据探索性分析、数据预处理和建模过程,本篇我们再来看看这些过程,对其中一些细节做一些优化。
1.项目概况介绍
1.1 背景介绍
Rossmann 成立于 1972 年,是德国最大的日化用品超市,在 7 个欧洲国家有 3000 多家药店。商店不定时会举办短期的促销活动以及连续的促销活动以此来提高销售额。除此之外,商店的销售还受到许多因素的影响,包括促销、竞争、学校和国家假日、季节性和周期性。

可靠的销售预测使商店经理能够创建有效的员工时间表,从而提高生产力和动力,比如更好的调整供应链和合理的促销策略与竞争策略,具有重要的实用价值与战略意义。如果可以帮助 Rossmann 创建一个强大的预测模型,将帮助仓库管理人员专注于对他们最重要的内容:客户和团队。
本次的任务是希望建立机器学习模型,通过给出的数据来预测 Rossmann 德国各地 1115 家店铺的 6 周销量。
1.2 数据介绍
数据以 1115 家 Rossmann 连锁商店为研究对象,从 2013 年 1 月 1 日到 2015 年 7 月共计录 1017209 条销售数据(27 个特征)。
数据集一共涵盖了四个文件:
train.csv:含有销量的历史数据。test.csv:未含销量的历史数据。sample_submission.csv:以正确格式提交的示例文件。store.csv:关于每个商店的一些补充信息。
其中,train.csv中的数据中一共含有 9 列信息:
store:为对应店铺的 id 序号。DayOfWeek:代表着每周开店的天数。Data:是对应销售额 Sales 产生的日期。Sales:就是销售额的历史数据。Customers:为进店的客人数量。Open:则表示这个店铺是否开门与否。Promo:表示商店是否在当天有促销活动。StateHoliday:与 SchoolHoliday 分别表示了是否是国定假日或是学校假日。
(1) 训练集
我们在 kaggle 的 data 页面下部的数据概览可以大致查看每个数据的分布情况(下为训练集train.csv情况)和部分数据样例如下:

(2) 测试集
test.csv 中的数据列几乎和train.csv一样,但缺少了 Sales(也就是销售数据)以及 Customers(用户流量)这两列。而我们的最终目标就是利用test.csv以及store.csv中的补充信息预测出test.csv中缺失的 Sales 数据。
test.csv的数据分布情况,可以看到和上面相比缺少了 Sales 以及与 Sales 有强关联的 Customer 数据。
数据分布和部分示例数据如下:

(3) 结果文件
结果文件sample_submission.csv中仅有 id 与 Sales 这两列,这个文件是我们将我们的预测答案提交至 Kaggle 的判题器上的标准格式模板。
在 Python 中我们只需要打开此文件,并将预测数据按照顺序填入 Sales 这一列后,使用Dataframe.to_csv(‘sample_submission.csv‘)后就可以将带有预测数据的sample_submission.csv保存至本地并准备后续上传。

(4) 商店信息
大家可以看到,train.csv与test.csv中有对应的店铺 id,这些店铺 id 的详细情况就对应在store.csv中,其中记录了一些店铺的地理位置信息以及营促销信息。
store.csv的数据分布情况,可以注意到这里有很多离散的类别标签。
数据分布和部分示例数据如下:

其中:
Store:对应表示了店铺的编号。StoreType:店铺的种类,一共有 a、b、c、d 四种不同种类的店铺。大家可以把它想象成快闪店,普通营业店,旗舰店,或 mini 店这样我们生活中的类型。Assortment:用 a、b、c 三种分类描述店铺内售卖产品的组合级别。例如旗舰店和 mini 店中组合的产品肯定是有很大不同的。Competition Distance、Competition Open Since Year、Competition Open Since Month:分别表示最近的竞争对手的店铺距离,开店时间(以年计算),开店时间(以月计算)。Promo2:描述该店铺是否有长期的促销活动。Promo2 Since Year于Promo2 Since Week:分别表示商店开始参与促销的年份和日历周。Promo Interval:描述promo2开始的连续间隔,以促销重新开始的月份命名。
1.3 项目目标
在了解了这些数据后我们就需要明确一下我们的项目目的,在 Rossmanns 销售预测中,我们需要利用历史数据,也就是train.csv中的数据进行监督学习。训练出的模型利用通 test.csv 中的数据进行模型推断(预测),将预测出的数据以sample_submission.csv的格式提交至 Kaggle 进行评分。在这过程中还可以结合store.csv中的补充信息加强我们模型获得数据的能力。
1.4 评估准则
模型所采纳的评估指标为 Kaggle 在竞赛中所推荐的 Root Mean Square Percentage Error (RMSPE)指标。
R M S P E = 1 n ∑ i = 1 n ( y i − y ^ i y i ) 2 = 1 n ∑ i = 1 n ( y ^ i y i − 1 ) 2 RMSPE = \sqrt{\frac{1}{n}\sum\limits_{i=1}^n\left(\frac{y_i-\hat{y}i}{y_i}\right)²} = \sqrt{\frac{1}{n}\sum\limits^n\left(\frac{\hat{y}_i}{{y}_i}-1\right)²} RMSPE=n1i=1∑n(yiyi−y^i)2 =n1i=1∑n(yiy^i−1)2
其中:
- y i y_i yi 代表门店当天的真实销售额。
- y ^ i \hat{y}_i y^i 代表相对应的预测销售额。
- n n n 代表样本的数量。
如果有任何一天的销售额为 0,那么将会被忽略。最后计算得到的这个 RMSPE 值越小代表误差就越小,相应就会获得更高的评分。
1.5 解决方案核心板块
本篇我们的解决方案,分成以下几个板块。

- Step 1: 加载数据
- Step 2: 探索性数据分析
- Step 3: 数据预处理(缺失值)
- Step 4: 特征工程
- Step 5: 基准模型与评估
- Step 6: XGBoost 建模
# 载入必要的库
import pandas as pd
import numpy as np
import xgboost as xgb
import missingno as msno
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
1.6 加载数据
Rossmann 场景建模数据包含很多信息维度,比如客户数量、假期等。又根据其任务目标可以判定为监督学习中典型的回归类建模问题。我们先对加载数据再做后续分析挖掘建模。
# 载入数据
train = pd.read_csv('./rossmann-store-sales/train.csv')
test = pd.read_csv('./rossmann-store-sales/test.csv')
store = pd.read_csv('./rossmann-store-sales/store.csv')
通过DataFrame.info()操作可以查看 DataFrame 的数据基本信息(数值分布、缺失值情况等)。详细的 pandas 操作也欢迎大家查看ShowMeAI的 数据分析系列教程 和 数据科学工具速查 | Pandas 使用指南。
下图的操作结果显示test.csv和store.csv中均存在缺失值,我们可以进行相应的预处理。
train.info(), test.info(), store.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1017209 entries, 0 to 1017208
Data columns (total 9 columns):
Store 1017209 non-null int64
DayOfWeek 1017209 non-null int64
Date 1017209 non-null object
Sales 1017209 non-null int64
Customers 1017209 non-null int64
Open 1017209 non-null int64
Promo 1017209 non-null int64
StateHoliday 1017209 non-null object
SchoolHoliday 1017209 non-null int64
dtypes: int64(7), object(2)
memory usage: 69.8+ MB
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 41088 entries, 0 to 41087
Data columns (total 8 columns):
Id 41088 non-null int64
Store 41088 non-null int64
DayOfWeek 41088 non-null int64
Date 41088 non-null object
Open 41077 non-null float64
Promo 41088 non-null int64
StateHoliday 41088 non-null object
SchoolHoliday 41088 non-null int64
dtypes: float64(1), int64(5), object(2)
memory usage: 2.5+ MB
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1115 entries, 0 to 1114
Data columns (total 10 columns):
Store 1115 non-null int64
StoreType 1115 non-null object
Assortment 1115 non-null object
CompetitionDistance 1112 non-null float64
CompetitionOpenSinceMonth 761 non-null float64
CompetitionOpenSinceYear 761 non-null float64
Promo2 1115 non-null int64
Promo2SinceWeek 571 non-null float64
Promo2SinceYear 571 non-null float64
PromoInterval 571 non-null object
dtypes: float64(5), int64(2), object(3)
memory usage: 87.2+ KB
2. 探索性数据分析
我们先对目标结果 sales 做一点分析,先对其进行分布绘图
train.loc[train.Open==0].Sales.hist(align='left')

发现:当店铺关闭时,日销量必然为 0。
fig = plt.figure(figsize=(16,6))
ax1 = fig.add_subplot(121)
ax1.set_xlabel('Sales')
ax1.set_ylabel('Count')
ax1.set_title('Sales of Closed Stores')
plt.xlim(-1,1)
train.loc[train.Open==0].Sales.hist(align='left')
ax2 = fig.add_subplot(122)
ax2.set_xlabel('Sales')
ax2.set_ylabel('PDF')
ax2.set_title('Sales of Open Stores')
sns.distplot(train.loc[train.Open!=0].Sales)
print('The skewness of Sales is {}'.format(train.loc[train.Open!=0].Sales.skew()))
The skewness of Sales is 1.5939220392699809

去掉店铺关闭时的数据之后,重新绘制店铺开启时的日销量分布图。可以发现日销量表现为明显的有偏分布,其偏度约为 1.594,远大于 0.75,我们会考虑对数据分布做预处理调整。
下面我们只采用店铺营业(Open!=0)时的数据进行训练。
train = train.loc[train.Open != 0]
train = train.loc[train.Sales > 0].reset_index(drop=True)
train.shape
(844338, 9)
3. 缺失值处理
# 训练集的缺失信息:无缺失
train[train.isnull().values==True]
| Store | DayOfWeek | Date |Sales |Customers |Open |Promo |StateHoliday | SchoolHoliday |
|–|–|–|–|–|–|–|–|–|–|
# 测试集的缺失信息
test[test.isnull().values==True]
| Id | Store | DayOfWeek | Date | Open | Promo | StateHoliday | SchoolHoliday |
| --|–|–|–|–|–|–|–|–|
| 479 | 480 | 622 | 4 | 2015/9/17 | NaN | 1 | 0 | 0 |
| 1335 | 1336 | 622 | 3 | 2015/9/16 | NaN | 1 | 0 | 0 |
| 2191 | 2192 | 622 | 2 | 2015/9/15 | NaN | 1 | 0 | 0 |
| 3047 | 3048 | 622 | 1 | 2015/9/14 | NaN | 1 | 0 | 0 |
| 4759 | 4760 | 622 | 6 | 2015/9/12 | NaN | 0 | 0 | 0 |
| 5615 | 5616 | 622 | 5 | 2015/9/11 | NaN | 0 | 0 | 0 |
| 6471 | 6472 | 622 | 4 | 2015/9/10 | NaN | 0 | 0 | 0 |
| 7327 | 7328 | 622 | 3 | 2015/9/9 | NaN | 0 | 0 | 0 |
| 8183 | 8184 | 622 | 2 | 2015/9/8 | NaN | 0 | 0 | 0 |
| 9039 | 9040 | 622 | 1 | 2015/9/7 | NaN | 0 | 0 | 0 |
| 10751 | 10752 | 622 | 6 | 2015/9/5 | NaN | 0 | 0 | 0 |
下面我们看一下 store 的缺失情况
# store 的缺失信息
msno.matrix(store)

test.csv与store.csv中都有缺失值,我们会对其进行处理,并对特征进行合并:
# 默认 test 中的店铺全部正常营业
test.fillna(1,inplace=True)
# 对 CompetitionDistance 中的缺失值采用中位数进行填补
store.CompetitionDistance = store.CompetitionDistance.fillna(store.CompetitionDistance.median())
# 对其它缺失值全部补 0
store.fillna(0,inplace=True)
我们知道,缺失值的一些处理方法包括:
- 删除字段(去除包含缺失值的 columns)。
- 填补缺失值(填入平均值、中位数或者拟合填充等)。
- 标记缺失值,把缺失值标记为特殊值(比如-999)或者新加一列标注某字段是否是缺失。
# 特征合并
train = pd.merge(train, store, on='Store')
test = pd.merge(test, store, on='Store')
train.head(10)
| Store | DayOfWeek | Date | Sales | Customers | Open | Promo | StateHoliday | SchoolHoliday | StoreType | Assortment | CompetitionDistance | CompetitionOpenSinceMonth | CompetitionOpenSinceYear | Promo2 | Promo2SinceWeek | Promo2SinceYear | PromoInterval
|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|
| 0 | 1 | 5 | 2015/7/31 | 5263 | 555 | 1 | 1 | 0 | 1 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 1 | 1 | 4 | 2015/7/30 | 5020 | 546 | 1 | 1 | 0 | 1 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 2 | 1 | 3 | 2015/7/29 | 4782 | 523 | 1 | 1 | 0 | 1 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 3 | 1 | 2 | 2015/7/28 | 5011 | 560 | 1 | 1 | 0 | 1 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 4 | 1 | 1 | 2015/7/27 | 6102 | 612 | 1 | 1 | 0 | 1 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 5 | 1 | 6 | 2015/7/25 | 4364 | 500 | 1 | 0 | 0 | 0 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 6 | 1 | 5 | 2015/7/24 | 3706 | 459 | 1 | 0 | 0 | 0 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 7 | 1 | 4 | 2015/7/23 | 3769 | 503 | 1 | 0 | 0 | 0 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 8 | 1 | 3 | 2015/7/22 | 3464 | 463 | 1 | 0 | 0 | 0 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
| 9 | 1 | 2 | 2015/7/21 | 3558 | 469 | 1 | 0 | 0 | 0 | c | a | 1270 | 9 | 2008 | 0 | 0 | 0 | 0 |
4. 特征工程
4.1 特征抽取函数
def build_features(features, data):
# 直接使用的特征
features.extend(['Store','CompetitionDistance','CompetitionOpenSinceMonth','StateHoliday','StoreType','Assortment',
'SchoolHoliday','CompetitionOpenSinceYear', 'Promo', 'Promo2', 'Promo2SinceWeek', 'Promo2SinceYear'])
# 以下特征处理方式参考:https://blog.csdn.net/aicanghai_smile/article/details/80987666
# 时间特征,抽取出年月日星期几等信息
features.extend(['Year','Month','Day','DayOfWeek','WeekOfYear'])
data['Year'] = data.Date.dt.year
data['Month'] = data.Date.dt.month
data['Day'] = data.Date.dt.day
data['DayOfWeek'] = data.Date.dt.dayofweek
data['WeekOfYear'] = data.Date.dt.weekofyear
# 'CompetitionOpen':竞争对手的已营业时间
# 'PromoOpen':竞争对手的已促销时间
# 两个特征的单位均为月
features.extend(['CompetitionOpen','PromoOpen'])
data['CompetitionOpen'] = 12*(data.Year-data.CompetitionOpenSinceYear) + (data.Month-data.CompetitionOpenSinceMonth)
data['PromoOpen'] = 12*(data.Year-data.Promo2SinceYear) + (data.WeekOfYear-data.Promo2SinceWeek)/4.0
data['CompetitionOpen'] = data.CompetitionOpen.apply(lambda x: x if x > 0 else 0)
data['PromoOpen'] = data.PromoOpen.apply(lambda x: x if x > 0 else 0)
# 'IsPromoMonth':该天店铺是否处于促销月,1 表示是,0 表示否
features.append('IsPromoMonth')
month2str = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul', 8:'Aug', 9:'Sept', 10:'Oct', 11:'Nov', 12:'Dec'}
data['monthStr'] = data.Month.map(month2str)
data.loc[data.PromoInterval==0, 'PromoInterval'] = ''
data['IsPromoMonth'] = 0
for interval in data.PromoInterval.unique():
if interval != '':
for month in interval.split(','):
data.loc[(data.monthStr == month) & (data.PromoInterval == interval), 'IsPromoMonth'] = 1
# 字符特征转换为数字
mappings = {'0':0, 'a':1, 'b':2, 'c':3, 'd':4}
data.StoreType.replace(mappings, inplace=True)
data.Assortment.replace(mappings, inplace=True)
data.StateHoliday.replace(mappings, inplace=True)
data['StoreType'] = data['StoreType'].astype(int)
data['Assortment'] = data['Assortment'].astype(int)
data['StateHoliday'] = data['StateHoliday'].astype(int)
4.2 特征抽取
# 处理 Date 方便特征提取
train.Date = pd.to_datetime(train.Date, errors='coerce')
test.Date = pd.to_datetime(test.Date, errors='coerce')
# 使用 features 数组储存使用的特征
features = []
# 对 train 与 test 特征提取
build_features(features, train)
build_features([], test)
# 打印使用的特征
print(features)
['Store', 'CompetitionDistance', 'CompetitionOpenSinceMonth', 'StateHoliday', 'StoreType', 'Assortment', 'SchoolHoliday', 'CompetitionOpenSinceYear', 'Promo', 'Promo2', 'Promo2SinceWeek', 'Promo2SinceYear', 'Year', 'Month', 'Day', 'DayOfWeek', 'WeekOfYear', 'CompetitionOpen', 'PromoOpen', 'IsPromoMonth']
5. 基准模型与评估
5.1 定义评估准则函数
由于需要预测连续值,因此需要采用回归模型。由于该项目是 Kaggle 赛题,测试集是使用根均方百分比误差(Root Mean Square Percentage Error,RMSPE)评测的,因此这里只能使用 RMSPE。RMSPE 的计算公式为:
R M S P E = 1 n ∑ i = 1 n ( y i − y ^ i y i ) 2 {\rm RMSPE} = \frac{1}{n}\sqrt{\sum\limits_{i = 1}^n {{{\left( {\frac{{{y_i} - {{\hat y}_i}}}{{{y_i}}}} \right)}²}}} RMSPE=n1i=1∑n(yiyi−y^i)2
其中 y i y_i yi 与 y ^ i {\hat y}_i y^i 分别为第 i i i 个样本标签的真实值与预测值。
# 评价函数 Rmspe
# 参考:https://www.kaggle.com/justdoit/xgboost-in-python-with-rmspe
def ToWeight(y):
w = np.zeros(y.shape, dtype=float)
ind = y != 0
w[ind] = 1./(y[ind]**2)
return w
def rmspe(yhat, y):
w = ToWeight(y)
rmspe = np.sqrt(np.mean(w * (y-yhat)**2))
return rmspe
def rmspe_xg(yhat, y):
y = y.get_label()
y = np.expm1(y)
yhat = np.expm1(yhat)
w = ToWeight(y)
rmspe = np.sqrt(np.mean(w * (y-yhat)**2))
return "rmspe", rmspe
def neg_rmspe(yhat, y):
y = np.expm1(y)
yhat = np.expm1(yhat)
w = ToWeight(y)
rmspe = np.sqrt(np.mean(w * (y-yhat)**2))
return -rmspe
5.2 基准模型评估
我们构建回归树模型作为基础模型进行建模和评估。回归树我们直接使用 SKLearn 的DecisionTreeRegressor即可,搭配 K 折交叉验证与网格搜索进行调参,主要调节的超参数是树的最大深度max_depth。
大家注意到这里的评估准则为neg_rmspe,这是恰当的传入模型调优的评估准则,GridSearchCV 默认找scoring_fnc最大的参数,而直接使用 rmspe 指标,其值越小,模型效果越好,因此应该取负,从而neg_rmspe值越大,模型精度越好。
from sklearn.model_selection import GridSearchCV, ShuffleSplit
from sklearn.metrics import make_scorer
from sklearn.tree import DecisionTreeRegressor
regressor = DecisionTreeRegressor(random_state=2)
cv_sets = ShuffleSplit(n_splits=5, test_size=0.2)
params = {'max_depth':range(10,40,2)}
scoring_fnc = make_scorer(neg_rmspe)
grid = GridSearchCV(regressor,params,scoring_fnc,cv=cv_sets)
grid = grid.fit(train[features], np.log1p(train.Sales))
DTR = grid.best_estimator_
# 显示最佳超参数
DTR.get_params()
{'criterion': 'mse',
'max_depth': 30,
'max_features': None,
'max_leaf_nodes': None,
'min_impurity_decrease': 0.0,
'min_impurity_split': None,
'min_samples_leaf': 1,
'min_samples_split': 2,
'min_weight_fraction_leaf': 0.0,
'presort': False,
'random_state': 2,
'splitter': 'best'}
# 生成上传文件
submission = pd.DataFrame({"Id": test["Id"], "Sales": np.expm1(DTR.predict(test[features]))})
submission.to_csv("benchmark.csv", index=False)
模型在测试集上的 Public Score 为0.18423,Private Score 为0.22081。下面使用 XGBoost 对基准测试结果进行提升。
6. XGBoost 建模与调优
6.1 模型参数
XGBoost 是比较强大的模型,可调参数较多(具体可以参考ShowMeAI文章 XGBoost 建模应用详解),我们主要调整下列的超参数:
eta:学习率。max_depth:单颗回归树的最大深度,较小导致欠拟合,较大导致过拟合。subsample:0-1 之间,控制每棵树随机采样的比例,减小这个参数的值,算法会更加保守,避免过拟合。但如果这个值设置得过小,可能会导致欠拟合。colsample_bytree:0-1 之间,用来控制每棵随机采样的特征的占比。num_trees:树的棵树,也就是迭代步数。
# # 默认的一版参数
# params = {'objective': 'reg:linear',
# 'eta': 0.01,
# 'max_depth': 11,
# 'subsample': 0.5,
# 'colsample_bytree': 0.5,
# 'silent': 1,
# 'seed': 1
# }
# num_trees = 10000
# 第二次调参,学习率过大,效果下降
# params = {"objective": "reg:linear",
# "booster" : "gbtree",
# "eta": 0.3,
# "max_depth": 10,
# "subsample": 0.9,
# "colsample_bytree": 0.7,
# "silent": 1,
# "seed": 1301
# }
# num_trees = 10000
# 第三次调参,步长适中,收敛速度快,结果优
params = {"objective": "reg:linear",
"booster" : "gbtree",
"eta": 0.1,
"max_depth": 10,
"subsample": 0.85,
"colsample_bytree": 0.4,
"min_child_weight": 6,
"silent": 1,
"thread": 1,
"seed": 1301
}
num_trees = 1200
6.2 模型训练
import numpy as np # 导入 numpy 包
from sklearn.model_selection import KFold # 从 sklearn 导入 KFold 包
#输入数据推荐使用 numpy 数组,使用 list 格式输入会报错
def K_Flod_spilt(K,fold,data):
'''
:param K: 要把数据集分成的份数。如十次十折取 K=10
:param fold: 要取第几折的数据。如要取第 5 折则 flod=5
:param data: 需要分块的数据
:param label: 对应的需要分块标签
:return: 对应折的训练集、测试集和对应的标签
'''
split_list = []
kf = KFold(n_splits=K)
for train, test in kf.split(data):
split_list.append(train.tolist())
split_list.append(test.tolist())
train,test=split_list[2 * fold],split_list[2 * fold + 1]
return data[train], data[test] #已经分好块的数据集
# 随机划分训练集与验证集
from sklearn.model_selection import train_test_split
#X_train, X_test = train_test_split(train, test_size=0.2, random_state=2)
X_train, X_test = K_Fold_spilt(10,5,train,label)
dtrain = xgb.DMatrix(X_train[features], np.log1p(X_train.Sales))
dvalid = xgb.DMatrix(X_test[features], np.log1p(X_test.Sales))
dtest = xgb.DMatrix(test[features])
watchlist = [(dtrain, 'train'),(dvalid, 'eval')]
gbm = xgb.train(params, dtrain, num_trees, evals=watchlist, early_stopping_rounds=50, feval=rmspe_xg, verbose_eval=False)
6.3 提交结果文件
# 生成提交文件
test_probs = gbm.predict(xgb.DMatrix(test[features]), ntree_limit=gbm.best_ntree_limit)
indices = test_probs < 0
test_probs[indices] = 0
submission = pd.DataFrame({"Id": test["Id"], "Sales": np.expm1(test_probs)})
submission.to_csv("xgboost.csv", index=False)
6.4 特征优化
在电商类场景中,过往的历史统计特征也非常重要,我们可以通过对历史销量数据,做不同时间粒度的统计构建统计特征作为补充信息,对于建模效果优化也有帮助,如下是一些示例:
sales_mean_bystore = X_train.groupby(['Store'])['Sales'].mean().reset_index(name='MeanLogSalesByStore')
sales_mean_bystore['MeanLogSalesByStore'] = np.log1p(sales_mean_bystore['MeanLogSalesByStore'])
sales_mean_bydow = X_train.groupby(['DayOfWeek'])['Sales'].mean().reset_index(name='MeanLogSalesByDOW')
sales_mean_bydow['MeanLogSalesByDOW'] = np.log1p(sales_mean_bydow['MeanLogSalesByStore'])
sales_mean_bymonth = X_train.groupby(['Month'])['Sales'].mean().reset_index(name='MeanLogSalesByMonth')
sales_mean_bymonth['MeanLogSalesByMonth'] = np.log1p(sales_mean_bymonth['MeanLogSalesByMonth'])
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模

机器学习实战 | 机器学习特征工程最全解读

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/208
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言

上图为大家熟悉的机器学习建模流程图,ShowMeAI在前序机器学习实战文章 Python 机器学习算法应用实践中和大家讲到了整个建模流程非常重要的一步,是对于数据的预处理和特征工程,它很大程度决定了最后建模效果的好坏,在本篇内容汇总,我们给大家展开对数据预处理和特征工程的实战应用细节做一个全面的解读。
特征工程
首先我们来了解一下「特征工程」,事实上大家在ShowMeAI的实战系列文章 Python 机器学习综合项目-电商销量预估 和 Python 机器学习综合项目-电商销量预估<进阶> 中已经看到了我们做了特征工程的处理。
如果我们对特征工程(feature engineering)做一个定义,那它指的是:利用领域知识和现有数据,创造出新的特征,用于机器学习算法;可以手动(manual)或自动(automated)。
- 特征:数据中抽取出来的对结果预测有用的信息。
- 特征工程:使用专业背景知识和技巧处理数据,使得特征能在机器学习算法上发挥更好的作用的过程。
在业界有一个很流行的说法:
数据与特征工程决定了模型的上限,改进算法只不过是逼近这个上限而已。

这是因为,在数据建模上,「理想状态」和「真实场景」是有差别的,很多时候原始数据并不是规矩干净含义明确充分的形态:

而特征工程处理,相当于对数据做一个梳理,结合业务提取有意义的信息,以干净整齐地形态进行组织:

特征工程有着非常重要的意义:
- 特征越好,灵活性越强。只要特征选得好,即使是一般的模型(或算法)也能获得很好的性能,好特征的灵活性在于它允许你选择不复杂的模型,同时运行速度也更快,也更容易理解和维护。
- 特征越好,构建的模型越简单。有了好的特征,即便你的参数不是最优的,你的模型性能也能仍然会表现的很好,所以你就不需要花太多的时间去寻找最优参数,这大大的降低了模型的复杂度,使模型趋于简单。
- 特征越好,模型的性能越出色。显然,这一点是毫无争议的,我们进行特征工程的最终目的就是提升模型的性能。
本篇内容,ShowMeAI带大家一起来系统学习一下特征工程,包括「特征类型」「数据清洗」「特征构建」「特征变换」「特征选择」等板块内容。
我们这里用最简单和常用的Titanic 数据集给大家讲解。
Titanic 数据集是非常适合数据科学和机器学习新手入门练习的数据集,数据集为 1912 年泰坦尼克号沉船事件中一些船员的个人信息以及存活状况。我们可以根据数据集训练出合适的模型并预测新数据(测试集)中的存活状况。
Titanic 数据集可以通过 seaborn 工具库直接加载,如下代码所示:
import pandas as pd
import numpy as np
import seaborn as sns
df_titanic = sns.load_dataset('titanic')
其中数据集的数据字段描述如下图所示:

1.特征类型
在具体演示 Titanic 的数据预处理与特征工程之前,ShowMeAI再给大家构建一些关于数据的基础知识。
1.1 结构化 vs 非结构化数据
数据可以分为「结构化数据」和「非结构化数据」,比如在互联网领域,大部分存储在数据库内的表格态业务数据,都是结构化数据;而文本、语音、图像视频等就属于非结构化数据。

1.2 定量 vs 定性数据
对于我们记录到的数据,我们通常又可以以「定量数据」和「定性数据」对齐进行区分,其中:
- 定量数据:指的是一些数值,用于衡量数量与大小。
- 例如高度,长度,体积,面积,湿度,温度等测量值。
- 定性数据:指的是一些类别,用于描述物品性质。
- 例如纹理,味道,气味,颜色等。

如下图是两类数据示例以及它们常见的处理分析方法的总结:

2.数据清洗
实际数据挖掘或者建模之前,我们会有「数据预处理」环节,对原始态的数据进行数据清洗等操作处理。因为现实世界中数据大体上都是不完整、不一致的「脏数据」,无法直接进行数据挖掘,或者挖掘结果差强人意。
「脏数据」产生的主要成因包括:
- 篡改数据
- 数据不完整
- 数据不一致
- 数据重复
- 异常数据
数据清洗过程包括数据对齐、缺失值处理、异常值处理、数据转化等数据处理方法,如下图所示:

下面我们注意对上述提到的处理方法做一个讲解。
2.1 数据对齐
采集到的原始数据,格式形态不一,我们会对时间、字段以及相关量纲等进行数据对齐处理,数据对齐和规整化之后的数据整齐一致,更加适合建模。如下图为一些处理示例:

(1) 时间
- 日期格式不一致【
2022-02-20、20220220、2022/02/20、20/02/2022】。 - 时间戳单位不一致,有的用秒表示,有的用毫秒表示。
- 使用无效时间表示,时间戳使用 0 表示,结束时间戳使用 FFFF 表示。
(2) 字段
- 姓名写了性别,身份证号写了手机号等。
(3) 量纲
- 数值类型统一【如 1、2.0、3.21E3、四】。
- 单位统一【如 180cm、1.80m】。
2.2 缺失值处理
数据缺失是真实数据中常见的问题,因为种种原因我们采集到的数据并不一定是完整的,我们有一些缺失值的常见处理方式:
具体的处理方式可以展开成下图:

下面回到我们的 Titanic 数据集,我们演示一下各种方法:
我们先对数据集的缺失值情况做一个了解(汇总分布):
df_titanic.isnull().sum()
survived 0
pclass 0
sex 0
age 177
sibsp 0
parch 0
fare 0
embarked 2
class 0
who 0
adult_male 0
deck 688
embark_town 2
alive 0
alone 0
(1) 删除
最直接粗暴的处理是剔除缺失值,即将存在遗漏信息属性值的对象 (字段,样本/记录) 删除,从而得到一个完备的信息表。优缺点如下:
- 优点:简单易行,在对象有多个属性缺失值、被删除的含缺失值的对象与初始数据集的数据量相比非常小的情况下有效;
- 不足:当缺失数据所占比例较大,特别当遗漏数据非随机分布时,这种方法可能导致数据发生偏离,从而引出错误的结论。
在我们当前 Titanic 的案例中,embark_town字段有 2 个空值,考虑删除缺失处理下。
df_titanic[df_titanic["embark_town"].isnull()]
df_titanic.dropna(axis=0,how='any',subset=['embark_town'],inplace=True)

(2) 数据填充
第 2 大类是我们可以通过一些方法去填充缺失值。比如基于统计方法、模型方法、结合业务的方法等进行填充。

① 手动填充
根据业务知识来进行人工手动填充。
② 特殊值填充
将空值作为一种特殊的属性值来处理,它不同于其他的任何属性值。如所有的空值都用unknown填充。一般作为临时填充或中间过程。
代码实现
df_titanic['embark_town'].fillna('unknown', inplace=True)
③ 统计量填充
若缺失率较低,可以根据数据分布的情况进行填充。常用填充统计量如下:
- 中位数:对于数据存在倾斜分布的情况,采用中位数填补缺失值。
- 众数:离散特征可使用众数进行填充缺失值。
- 平均值:对于数据符合均匀分布,用该变量的均值填补缺失值。
中位数填充——fare:缺失值较多,使用中位数填充

df_titanic['fare'].fillna(df_titanic['fare'].median(), inplace=True)
众数填充——embarked:只有两个缺失值,使用众数填充
df_titanic['embarked'].isnull().sum()
#执行结果:2
df_titanic['embarked'].fillna(df_titanic['embarked'].mode(), inplace=True)
df_titanic['embarked'].value_counts()
#执行结果:
#S 64
同类均值填充
age:根据 sex、pclass 和 who 分组,如果落在相同的组别里,就用这个组别的均值或中位数填充。
df_titanic.groupby(['sex', 'pclass', 'who'])['age'].mean()
age_group_mean = df_titanic.groupby(['sex', 'pclass', 'who'])['age'].mean().reset_index()
def select_group_age_median(row):
condition = ((row['sex'] == age_group_mean['sex']) &
(row['pclass'] == age_group_mean['pclass']) &
(row['who'] == age_group_mean['who']))
return age_group_mean[condition]['age'].values[0]
df_titanic['age'] =df_titanic.apply(lambda x: select_group_age_median(x) if np.isnan(x['age']) else x['age'],axis=1)

④ 模型预测填充
如果其他无缺失字段丰富,我们也可以借助于模型进行建模预测填充,将待填充字段作为 Label,没有缺失的数据作为训练数据,建立分类/回归模型,对待填充的缺失字段进行预测并进行填充。

最近距离邻法(KNN)
- 先根据欧式距离或相关分析来确定距离具有缺失数据样本最近的 K K K 个样本,将这 K K K 个值加权平均/投票来估计该样本的缺失数据。
回归(Regression)
- 基于完整的数据集,建立回归方程。对于包含空值的对象,将已知属性值代入方程来估计未知属性值,以此估计值来进行填充。当变量不是线性相关时会导致有偏差的估计,常用线性回归。
我们以 Titanic 案例中的 age 字段为例,讲解一下:
- age 缺失量较大,这里我们用 sex、pclass、who、fare、parch、sibsp 六个特征构建随机森林模型,填充年龄缺失值。
df_titanic_age = df_titanic[['age', 'pclass', 'sex', 'who','fare', 'parch', 'sibsp']]
df_titanic_age = pd.get_dummies(df_titanic_age)
df_titanic_age.head()
# 乘客分成已知年龄和未知年龄两部分
known_age = df_titanic_age[df_titanic_age.age.notnull()]
unknown_age = df_titanic_age[df_titanic_age.age.isnull()]
# y 即目标年龄
y_for_age = known_age['age']
# X 即特征属性值
X_train_for_age = known_age.drop(['age'], axis=1)
X_test_for_age = unknown_age.drop(['age'], axis=1)
from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X_train_for_age, y_for_age)
# 用得到的模型进行未知年龄结果预测
y_pred_age = rfr.predict(X_test_for_age)
# 用得到的预测结果填补原缺失数据
df_titanic.loc[df_titanic.age.isnull(), 'age'] = y_pred_age
sns.distplot(df_titanic.age)

⑤ 插值法填充
还可以用插值法对数据填充,细分一下包括线性插值、多重插补、热平台插补、拉格朗日插值、牛顿插值等。

线性插值法
使用插值法可以计算缺失值的估计值,所谓的插值法就是通过两点 ( x 0 , y 0 ) (x_0, y_0) (x0,y0), ( x 1 , y 1 ) (x_1, y_1) (x1,y1) 估计中间点的值。假设 y = f ( x ) y=f(x) y=f(x) 是一条直线,通过已知的两点来计算函数 f ( x ) f(x) f(x),然后只要知道 x x x 就能求出 y y y,以此方法来估计缺失值。

.interpolate(method = 'linear', axis)方法将通过linear插值使用沿着给定axis的值替换 NaN 值,这个差值也就是前后或者上下的中间值
df_titanic['fare'].interpolate(method = 'linear', axis = 0)
同时,也可用行值插入
df_titanic['fare'].interpolate(method = 'linear', axis = 1)
多重插补(Multiple Imputation)
多值插补的思想来源于贝叶斯估计,认为待插补的值是随机的,它的值来自于已观测到的值。具体实践上通常是估计出待插补的值,然后再加上不同的噪声,形成多组可选插补值。根据某种选择依据,选取最合适的插补值。

多重插补方法分为三个步骤:
- ① 为每个空值产生一套可能的插补值,这些值反映了无响应模型的不确定性;每个值都可以被用来插补数据集中的缺失值,产生若干个完整数据集合;
- ② 每个插补数据集合都用针对完整数据集的统计方法进行统计分析;
- ③ 对来自各个插补数据集的结果,根据评分函数进行选择,产生最终的插补值。
⑥ 哑变量填充
有另外一种非常有意思的填充方式,叫做「哑变量填充」,在变量为离散型,且不同值较少的情况下可以采用,以 Titanic 数据为例:
- 性别 SEX 变量,存在 male,fameal,NA(缺失)三个不同的值,可将该列转换成
IS_SEX_MALE、IS_SEX_FEMALE、IS_SEX_NA。 - 若某个变量存在十几个不同的值,可根据每个值的频数,将频数较小的值归为一类
other,降低维度。此做法可最大化保留变量的信息。

以下为参考代码示例:
sex_list = ['MALE', 'FEMALE', np.NaN, 'FEMALE', 'FEMALE', np.NaN, 'MALE']
df = pd.DataFrame({'SEX': sex_list})
display(df)
df.fillna('NA', inplace=True)
df = pd.get_dummies(df['SEX'],prefix='IS_SEX')
display(df)
# 原始数据
SEX
0 MALE
1 FEMALE
2 NaN
3 FEMALE
4 FEMALE
5 NaN
6 MALE
# 填充后
IS_SEX_FEMALE IS_SEX_MALE IS_SEX_NA
0 0 1 0
1 1 0 0
2 0 0 1
3 1 0 0
4 1 0 0
5 0 0 1
6 0 1
当特征值缺失超过 80 % 80% 80% 以上,建议删除【或加入「是」「否」标记位信息】,容易影响模型效果
df_titanic.drop(["deck"],axis=1)
2.3 异常值处理
数据质量也会很大程度影响机器学习应用效果,数据的错误值或异常值可能会造成测量误差或异常系统条件的结果,给模型学习带来很大的问题。实际我们很多时候会有异常值检测与处理环节,下面给大家做一个梳理。
(1) 异常检测方法
① 基于统计分析
通常用户用某个统计分布对数据点进行建模,再以假定的模型,根据点的分布来确定是否异常。
如通过分析统计数据的散度情况,即数据变异指标,对数据的分布情况有所了解,进而通过数据变异指标来发现数据中的异常点数据。
常用的数据变异指标有极差、四分位数间距、均差、标准差、变异系数等等,如变异指标的值大表示变异大、散布广;值小表示离差小,较密集。
比如,最大最小值可以用来判断这个变量的取值是否超过了合理的范围,如客户的年龄为 − 20 -20 −20 岁或 200 200 200 岁,为异常值。
② 3σ原则
如果数据近似正态分布,在 3 σ 3 \sigma 3σ 原则下,异常值为一组测定值中与平均值的偏差超过 3 3 3 倍标准差的值。
- 如果数据服从正态分布,距离平均值 3 σ 3 \sigma 3σ 之外的值出现的概率为 P ( ∣ x − μ ∣ > 3 σ ) ≤ 0.003 P(\left | x-\mu \right | >3\sigma) \le 0.003 P(∣x−μ∣>3σ)≤0.003,属于极个别的小概率事件。
- 如果数据不服从正态分布,也可以用远离平均值的多少倍标准差来描述。

③ 箱线图分析
大家还记得在数据分析部分有一个很有效的工具叫做箱线图,箱线图提供了识别异常值的一个标准:如果一个值小于 Q 1 − 1.5 I Q R Q_1-1.5IQR Q1−1.5IQR 或大于 Q 3 + 1.5 I Q R Q_3+1.5IQR Q3+1.5IQR 的值,则被称为异常值。
- Q 1 Q_1 Q1 为下四分位数,表示全部观察值中有四分之一的数据取值比它小;
- Q 4 Q_4 Q4 为上四分位数,表示全部观察值中有四分之一的数据取值比它大;
- I Q R IQR IQR 为四分位数间距,是上四分位数 Q 1 Q_1 Q1 与下四分位数 Q 3 Q_3 Q3 的差值,包含了全部观察值的一半。
箱型图判断异常值的方法以四分位数和四分位距为基础,四分位数具有鲁棒性: 25 % 25 % 25% 的数据可以变得任意远并且不会干扰四分位数,所以异常值不能对这个标准施加影响。因此箱型图识别异常值比较客观,在识别异常值时有一定的优越性。

sns.catplot(y="fare",x="survived", kind="box", data=df_titanic,palette="Set2")

④ 基于模型检测
我们也可以基于模型对异常值检测,基本思路是先建立一个数据模型,那些同模型不能完美拟合的对象就视作异常。
- 如果模型是簇的集合,则异常是不显著属于任何簇的对象。
- 在使用回归模型时,异常是相对远离预测值的对象。

优点:有坚实的统计学理论基础,当存在充分的数据和所用的检验类型的知识时,这些检验可能非常有效。
缺点:对于多元数据,可用的选择少一些,并且对于高维数据,这些检测可能性很差。
⑤ 基于距离
我们还有基于距离的方法可以用于异常检测。这类方法基于下面这个假设:如果一个数据对象和大多数点距离都很远,那这个对象就是异常。通过定义对象之间的临近性度量,根据距离判断异常对象是否远离其他对象,主要使用的距离度量方法有绝对距离(曼哈顿距离)、欧氏距离和马氏距离等方法。

-
优点:
-
- 基于距离的方法比基于统计类方法要简单得多;因为为一个数据集合定义一个距离的度量要比确定数据集合的分布容易的多。
-
缺点:
-
- 基于邻近度的方法需要 O ( m 2 ) O(m2) O(m2) 时间,大数据集不适用;
- 该方法对参数的选择也是敏感的;
- 不能处理具有不同密度区域的数据集,因为它使用全局阈值,不能考虑这种密度的变化。
⑥ 基于密度
一个很直接的异常检测思路是基于分布密度来做,具体为:考察当前点周围密度,局部异常点/离群点的局部密度显著低于大部分近邻点。这类方法适用于非均匀的数据集。

-
优点:
- 给出了对象是离群点的定量度量,并且即使数据具有不同的区域也能够很好的处理。
-
缺点:
- 与基于距离的方法一样,这些方法必然具有 O ( m 2 ) O(m2) O(m2) 的时间复杂度。
- 对于低维数据使用特定的数据结构可以达到 O ( m l o g m ) O(mlogm) O(mlogm);
- 参数选择困难。
- 虽然算法通过观察不同的 k 值,取得最大离群点得分来处理该问题,但是,仍然需要选择这些值的上下界。
⑦ 基于聚类
我们可以基于聚类的方法进行异常检测,远离 cluster 的样本更可能是异常值。
不过该方法会受到聚类 cluster 个数 k k k 的影响,一种策略是对于不同的簇个数重复该分析;另一种方法是找出大量小簇,其想法是:
- 较小的簇倾向于更加凝聚;
- 如果存在大量小簇时一个对象是异常点,则它多半是一个真正的异常点。
- 不利的一面是一组异常点可能形成小簇而逃避检测。

-
优点:
- 基于线性和接近线性复杂度(k 均值)的聚类技术来发现离群点可能是高度有效的;
- 簇的定义通常是离群点的补,因此可能同时发现簇和离群点。
-
缺点:
- 产生的离群点集和它们的得分可能非常依赖所用的簇的个数和数据中离群点的存在性;
- 聚类算法产生的簇的质量对该算法产生的离群点的质量影响非常大。
⑧ 基于邻近度的异常点检测
同样的,我们也有基于近邻度的思路来做异常检测,我们认为异常点远离大部分的点。这种方法比统计学方法更一般、更容易使用,因为确定数据集的有意义的邻近性度量比确定它的统计分布更容易。一个对象的异常点得分由到它的 K − K- K− 最近邻的距离给定,所以异常点得分对 K K K 的取值高度敏感:
- 如果 K K K 太小(例如 1 1 1),则少量的邻近异常异常点可能导致较异常低的异常点得分。
- 如果 K K K 太大,则点数少于 K K K 的簇中所有的对象可能都成了异常异常点。
为了使该方案对于 K K K 的选取更具有鲁棒性,可以使用 K K K 个最近邻的平均距离。

优点:
- 简单
缺点:
- 基于邻近度的方法需要 O ( m 2 ) O(m2) O(m2) 时间,大数据集不适用;
- 该方法对参数的选择也是敏感的;
- 不能处理具有不同密度区域的数据集,因为它使用全局阈值,不能考虑这种密度的变化。
在数据处理阶段将离群点作为影响数据质量的异常点考虑,而不是作为通常所说的异常检测目标点,一般采用较为简单直观的方法,结合箱线图和 MAD 的统计方法判断变量的离群点。如下为绘制散点图根据分布直接判断。
sns.scatterplot(x="fare", y="age", hue="survived",data=df_titanic,palette="Set1")

(2) 异常处理方法
对异常值处理,需要具体情况具体分析,异常值处理方法常用的有以下几种:
- 删除含有异常值的记录;
- 某些筛选出来的异常样本是否真的是不需要的异常特征样本,最好结合业务再确认一编,防止正常样本被过滤。
- 将异常值视为缺失值,交给缺失值处理方法来处理;
- 使用均值/中位数/众数来修正;
- 不处理。
3.特征构建
前序的数据预处理过程能保证我们拿到干净整齐准确的数据,但这些数据未必对于建模是最有效的,下一步我们通常会进行特征构建,结合业务场景产生衍生变量来提升数据表达能力和模型建模效果。
3.1 统计特征构建
统计特征是一类非常有效的特征,尤其在时序问题场景中,以下为统计特征构建的一些思考维度和方法:

- ① 基于业务规则、先验知识等构建新特征。
- ② 四分位数、中位数、平均值、标准差、偏差、偏度、偏锋、离散系统。
- ③ 构造长、短期统计量(如周、月)。
- ④ 时间衰减(越靠近观测权重值高)。
回到 Titanic 数据集,我们来看看结合业务理解,我们可以做哪些新特征:
年龄处理
我们对年龄 age 字段进行进一步处理,考虑到不同的年龄段对应的人群可能获救概率不同,我们根据年龄值分成不同区间段,对应到 child、young、midlife、old 等
def age_bin(x):
if x <= 18:
return 'child'
elif x <= 30:
return 'young'
elif x <= 55:
return 'midlife'
else:
return 'old'
df_titanic['age_bin'] = df_titanic['age'].map(age_bin)
df_titanic['age_bin'].unique()
执行结果:
array(['young', 'midlife', 'child', 'old'], dtype=object)
抽取「称呼」特征
我们在 name 字段里,可以看到各种不同的称呼,如「Mr」「Master」「Dr」等,这些称呼体现了乘客的身份等信息,我们可以对其做抽取构建新的特征。
# 提取称呼
df_titanic['title'] = df_titanic['name'].map(lambda x: x.split(',')[1].split('.')[0].strip())
df_titanic['title'].value_counts()
执行结果如下:
Mr 757
Miss 260
Mrs 197
Master 61
Rev 8
Dr 8
Col 4
Ms 2
Major 2
Mlle 2
Dona 1
Sir 1
Capt 1
Don 1
Lady 1
Mme 1
the Countess 1
Jonkheer 1
我们做一个简单的「称呼」统计
# 对称呼细分,是官员,还是皇室,还是女士、先生、小姐
df_titanic['title'].unique()
执行结果:
array(['Mr', 'Mrs', 'Miss', 'Master', 'Don', 'Rev', 'Dr', 'Mme', 'Ms',
'Major', 'Lady', 'Sir', 'Mlle', 'Col', 'Capt', 'the Countess',
'Jonkheer', 'Dona'], dtype=object)
下面我们对这些「称呼」「称谓」做一个规范化统一。
title_dictionary = {
"Mr": "Mr",
"Mrs": "Mrs",
"Miss": "Miss",
"Master": "Master",
"Don": "Royalty",
"Rev": "Officer",
"Dr": "Officer",
"Mme": "Mrs",
"Ms": "Mrs",
"Major": "Officer",
"Lady": "Royalty",
"Sir": "Royalty",
"Mlle": "Miss",
"Col": "Officer",
"Capt": "Officer",
"the Countess": "Royalty",
"Jonkheer": "Royalty",
"Dona": 'Mrs'
}
df_titanic['title'] = df_titanic['title'].map(title_dictionary)
df_titanic['title'].value_counts()
执行结果如下:
Mr 757
Miss 262
Mrs 201
Master 61
Officer 23
Royalty 5
抽取家庭规模
在 Titanic 上,有的成员之间有亲属关系,考虑到家族大小对于最终是否获救也有影响,我们可以构建一个 family_size 的特征,用于表征家庭规模。
df_titanic['family_size'] = df_titanic['sibsp'] + df_titanic['parch'] + 1
df_titanic['family_size'].head()
执行结果如下:
0 2
1 2
2 1
3 2
4 1
3.2 周期值
在电商等场景下,数据有一定的周期规律,我们可以提取一些周期值作为有效信息。

时序周期的一些考虑维度如下:
- ①前 n 个周期/天/月/年的周期值,如过去 5 天分位数、平均值等
- ②同比/环比
3.3 数据分桶
数据分桶,是对连续值属性处理的一种常用方法,它指的是我们把连续数值切段,并把连续值归属到对应的段中。数据分桶也叫做数据分箱或离散化。

(1) 等频、等距分桶
(a) 自定义分箱
指根据业务经验或者常识等自行设定划分的区间,然后将原始数据归类到各个区间中。
(b) 等距分箱
按照相同宽度将数据分成几等份。
从最小值到最大值之间,均分为 N N N 等份。如果 A A A、 B B B 为最小最大值,则每个区间的长度为 W = ( B − A ) / N W=(B−A)/N W=(B−A)/N,区间边界值为 A + W A+W A+W、 A + 2 W A+2W A+2W、 c d o t s cdots cdots、 A + ( N − 1 ) W A+(N−1)W A+(N−1)W。

等距分箱只考虑边界,每个等份里面的实例数量可能不等。等距分桶的缺点是受到异常值的影响比较大。
© 等频分箱
将数据分成几等份,每等份数据里面的个数是一样的。

在等频分箱中,区间的边界值要经过计算获得,最终每个区间包含大致相等的实例数量。比如说 N = 5 N=5 N=5,每个区间应该包含大约 20 % 20 % 20% 的实例。
- 数值变量分箱
我们先对船票价格做一个等频切分(大家如果对船票价格进行分布绘图,会发现是很长尾的分布,并不适合等距切分),看看分开的区间段。
# qcut 等频率分箱
df_titanic['fare_bin'], bins = pd.qcut(df_titanic['fare'], 5, retbins=True)
df_titanic['fare_bin'].value_counts()
结果如下:
(7.854, 10.5] 184
(21.679, 39.688] 180
(-0.001, 7.854] 179
(39.688, 512.329] 176
(10.5, 21.679] 172
bins #array([ 0\. , 7.8542, 10.5 , 21.6792, 39.6875, 512.3292])
下面根据区间段对其进行等频切分
# 对船票 fare 进行分段分桶
def fare_cut(fare):
if fare <= 7.8958:
return 0
if fare <= 10.5:
return 1
if fare <= 21.6792:
return 2
if fare <= 39.6875:
return 3
return 4
df_titanic['fare_bin'] = df_titanic['fare'].map(fare_cut)
相比船票价格,年龄 age 字段的分布更加集中,且区间大小比较明确,我们采用等距切分,代码如下:
# cut 等距离分箱
bins = [0, 12, 18, 65, 100]
pd.cut(df_titanic['age'], bins).value_counts
(2) Best-KS 分桶
- 1.将特征值值进行从小到大的排序。
- 2.计算出 K S KS KS 最大的那个值,即为切点,记为 D D D。然后把数据切分成两部分。
- 3.重复步骤 2,进行递归, D D D 左右的数据进一步切割。直到 K S KS KS 的箱体数达到我们的预设阈值即可。
- 4.连续型变量:分箱后的 K S KS KS 值 ≤ \le ≤ 分箱前的 K S KS KS 值
- 5.分箱过程中,决定分箱后的 K S KS KS 值是某一个切点,而不是多个切点的共同作用。这个切点的位置是原始 K S KS KS 值最大的位置。
(3) 卡方分桶
自底向上的(即基于合并的)数据离散化方法,依赖于卡方检验:具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。

基本思想:
如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。而低卡方值表明它们具有相似的类分布。
实现步骤:
- ①预先定义一个卡方的阈值
- ②初始化;根据要离散的属性对实例进行排序,每个实例属于一个区间
- ③合并区间
- 计算每一对相邻区间的卡方值
- 将卡方值最小的一对区间合并
(4) 最小熵法分箱
还有最小熵分箱法,需要使总熵值达到最小,也就是使分箱能够最大限度地区分因变量的各类别。
熵是信息论中数据无序程度的度量标准,提出信息熵的基本目的是找出某种符号系统的信息量和冗余度之间的关系,以便能用最小的成本和消耗来实现最高效率的数据存储、管理和传递。
数据集的熵越低,说明数据之间的差异越小,最小熵划分就是为了使每箱中的数据具有最好的相似性。给定箱的个数,如果考虑所有可能的分箱情况,最小熵方法得到的箱应该是具有最小熵的分箱。
3.4 特征组合
我们在有些场景下会考虑特征组合构建强特征,如下为常用的特征组合构建方式:
- 离散+离散:构建笛卡尔积(即两两组合「且」关系)。
- 离散+连续:连续特征分桶后进行笛卡尔积或基于类别特征 group by 构建统计特征。
- 连续+连续:加减乘除,多项式特征,二阶差分等。

- 多项式特征
针对连续值特征,我们对几个特征构建多项式特征,以达到特征组合与高阶增强的作用。

在 Titanic 的例子中,如下为数值型特征:
df_titanic_numerical = df_titanic[['age','sibsp','parch','fare','family_size']]
df_titanic_numerical.head()
我们可以参考下述代码构建多项式特征
# 扩展数值特征
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=False)
df_titanic_numerical_poly = poly.fit_transform(df_titanic_numerical)
pd.DataFrame(df_titanic_numerical_poly, columns=poly.get_feature_names()).head()

在构建完成特征后,我们查看下衍生新特征变量的相关性情况,下面的热力图 heatmap 里颜色越深相关性越大:
sns.heatmap(pd.DataFrame(df_titanic_numerical_poly, columns=poly.get_feature_names()).corr())

4.特征变换
我们对于构建完的特征,会做一些「特征变换」的操作,以适应不同的模型,更好地完成建模。

4.1 标准化(Standardization)
标准化操作也称作 Z-score 变换,它使数值特征列的算数平均为 0 0 0,方差(以及标准差)为 1 1 1,如下图所示。

注意:如果数值特征列中存在数值极大或极小的 outlier(通过 EDA 发现),应该使用更稳健(robust)的统计数据:用中位数而不是算术平均数,用分位数(quantile)而不是方差。这种标准化方法有一个重要的参数:(分位数下限,分位数上限),最好通过 EDA 的数据可视化确定。免疫 outlier。
标准化操作的参考代码如下:
from sklearn.preprocessing import StandardScale
#标准化模型训练
Stan_scaler = StandardScaler()
Stan_scaler.fit(x)
x_zscore = Stan_scaler.transform(x)
x_test_zscore = Stan_scaler.transform(x_test)
joblib.dump(Stan_scaler,'zscore.m') #写入文件
4.2 归一化(Normalization)
归一化操作会基于向量模长调整数据幅度大小,但并不会改变原始数据的顺序。如下图所示:

4.3 幅度缩放(scaling)
幅度缩放是为了让不同特征的取值在大体一致的数量级和数据区间内,比较常用的方法是最大最小值缩放,如下图所示:

下面为幅度缩放操作的参考代码:
from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
min_max_scaler.fit_transform(x)
x_minmax = min_max_scaler.transform(x)
x_test_minmax = min_max_scaler.transform(x_test)
joblib.dump(min_max_scaler,'min_max_scaler.m') #写入文件
4.4 归一化 VS 标准化
归一化和标准化是两个非常常见的特征变换操作,下面我们来对比一下标准化和归一化:
- 目的不同,归一化是为了消除纲量压缩到 [ 0 , 1 ] [0,1] [0,1] 区间;标准化只是调整特征整体的分布。
- 归一化与最大,最小值有关;标准化与均值,标准差有关。
- 归一化输出在 [ 0 , 1 ] [0,1] [0,1] 之间;标准化无限制。
它们分别的适用场景可以归纳总结如下:
-
在分类、聚类算法中(参考ShowMeAI教程 图解机器学习算法:从入门到精通系列教程),需要使用距离来度量相似性的时候(如 SVM、KNN)或者使用 PCA 技术进行降维的时候,标准化(Z-score standardization)表现更好。
-
在不涉及距离度量、协方差计算、数据不符合正太分布的时候,可以使用第一种方法或其他归一化方法。例如图像处理时,将 RGB 图像转换为灰度图像后将其值限定在 [ 0 , 255 ] [0,255] [0,255] 的范围。
-
基于树的模型(如随机森林、GBDT、XGBoost、LightGBM 等,具体模型参考ShowMeAI教程 图解机器学习算法:从入门到精通系列教程)不需要进行特征的归一化。如果是基于参数的模型或者基于距离的模型(逻辑回归、K-Means 聚类、神经网络等),因为需要对参数或者距离进行计算,都需要进行归一化。
4.5 非线性变换
我们在有些场景下,还会对数值字段进行分布调整或者校正,利用统计或数学变换来减轻数据分布倾斜的影响。使原本密集的区间的值尽可能的分散,原本分散的区间的值尽量的聚合。
大部分变换函数都属于幂变换函数簇,主要作用是稳定方差,保持分布接近于正态分布并使得数据与分布的平均值无关。
我们来看看一些典型的非线性统计变换。
(1) log 变换
log 变换通常用来创建单调的数据变换。主要作用为稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。
- log 变换倾向于拉伸那些落在较低的幅度范围内自变量值的范围,倾向于压缩或减少更高幅度范围内的自变量值的范围,从而使得倾斜分布尽可能的接近正态分布。
- 针对一些数值连续特征的方差不稳定,特征值重尾分布我们需要采用 log 化来调整整个数据分布的方差,属于方差稳定型数据转换。
log 变换属于幂变换函数簇,数学表达式为
y = l o g b ( x ) y=log_{b}(x) y=logb(x)

下面我们对 Titanic 数据集中的船票价格字段进行 log1p 变换,示例代码如下:
sns.distplot(df_titanic.fare,kde=False)

df_titanic['fare_log'] = np.log((1+df_titanic['fare']))
sns.distplot(df_titanic.fare_log,kde=False)

(2) box-cox 变换
box-cox 变换是 box 和 cox 在 1964 年提出的一种广义幂变换方法,是统计建模中常用的一种数据变换,用于连续的响应变量不满足正态分布的情况。box-cox 变换之后,可以一定程度上减小不可观测的误差和预测变量的相关性。
box-cox 变换的主要特点是引入一个参数,通过数据本身估计该参数进而确定应采取的数据变换形式,box-cox 变换可以明显地改善数据的正态性、对称性和方差相等性,对许多实际数据都是行之有效的。
box-cox 变换函数数学表达式如下:
y ( λ ) = { y λ − 1 λ , λ ≠ 0 ln y , λ = 0 y(\lambda)=\left{\begin{array}{ll} \frac{y^{\lambda}-1}{\lambda}, & \lambda \neq 0 \ \ln y, & \lambda=0 \end{array}\right. y(λ)={λyλ−1,lny,λ=0λ=0
生成的变换后的输出 y y y,是输入 x x x 和变换参数的函数;当 λ = 0 \lambda=0 λ=0 时,该变换就是自然对数 log 变换,前面我们已经提到过了。 λ \lambda λ 的最佳取值通常由最大似然或最大对数似然确定。

下面我们对 Titanic 数据集中的船票价格字段进行 box-cox 变换,示例代码如下:
# 从数据分布中移除非零值
fare_positive_value = df_titanic[(~df_titanic['fare'].isnull()) & (df_titanic['fare']>0)]['fare']
import scipy.stats as spstats
# 计算最佳λ值
l, opt_lambda = spstats.boxcox(fare_positive_value)
print('Optimal lambda value:', opt_lambda) # -0.5239075895755266
# 进行 Box-Cox 变换
fare_boxcox_lambda_opt = spstats.boxcox(df_titanic[df_titanic['fare']>0]['fare'],lmbda=opt_lambda)
sns.distplot(fare_boxcox_lambda_opt,kde=Fal

4.6 离散变量处理
对于类别型的字段特征(比如颜色、类型、好坏程度),有很多模型并不能直接处理,我们对其进行编码后能更好地呈现信息和支撑模型学习。有以下常见的类别型变量编码方式:
(1) 标签编码(label encoding)
标签编码(label encoding)是最常见的类别型数据编码方式之一,编码值介于 0 0 0 和 n_classes-1 之间的标签。

例如:比如有 [ d o g , c a t , d o g , m o u s e , r a b b i t ] [dog,cat,dog,mouse,rabbit] [dog,cat,dog,mouse,rabbit],我们把其转换为 [ 0 , 1 , 0 , 2 , 3 ] [0,1,0,2,3] [0,1,0,2,3]。
- 优点:相对于 OneHot 编码,LabelEncoder 编码占用内存空间小,并且支持文本特征编码。
- 缺点:它的编码方式给不同类别带来了额外的大小顺序关系,在有些计算型模型(比如逻辑回归)里有影响,它可以使用在树模型中。
标签编码的参考代码如下:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit(["超一线", "一线", "二线", "三线"])
print('特征:{}'.format(list(le.classes_)))
# 输出 特征:['一线', '三线', '二线', '超一线']
print('转换标签值:{}'.format(le.transform(["超一线", "一线", "二线"])))
# 输出 转换标签值:array([3 0 2]...)
print('特征标签值反转:{}'.format(list(le.inverse_transform([2, 2, 1]))))
# 输出 特征标签值反转:'二线', '二线', '三线
(2) 独热向量编码(one hot encoding )
独热编码通常用于处理类别间不具有大小关系的特征。
![机器学习; 数据处理与特征工程; 特征变换; 离散变量处理; 8-51
例如:特征:血型,一共有四种类别 ( A , B , A B , O ) (A,B,AB,O) (A,B,AB,O),采用独热编码后,会把血型变成有一个 4 维的稀疏向量
- A 表示为 [ 1 , 0 , 0 , 0 ] [1,0,0,0] [1,0,0,0]
- B 表示为 [ 0 , 1 , 0 , 0 ] [0,1,0,0] [0,1,0,0]
- AB 表示为 [ 0 , 0 , 1 , 0 ] [0,0,1,0] [0,0,1,0]
- O 表示为 [ 0 , 0 , 0 , 1 ] [0,0,0,1] [0,0,0,1]
最终生成的稀疏向量的维度,和类别数相同。
- 优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有 0 0 0 和 1 1 1,不同的类型存储在垂直的空间。
- 缺点:只能对数值型变量二值化,无法直接对字符串型的类别变量编码。当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用 PCA 来减少维度。而且 one hot encoding+PCA 这种组合在实际中也非常有用。
如果借助于 pandas 工具库(查看ShowMeAI的 数据分析系列教程 和 数据科学工具速查 | Pandas 使用指南 进行详细了解),独热向量编码的 Python 代码参考示例如下:
sex_list = ['MALE', 'FEMALE', np.NaN, 'FEMALE', 'FEMALE', np.NaN, 'MALE']
df = pd.DataFrame({'SEX': sex_list})
display(df)
df.fillna('NA', inplace=True)
df = pd.get_dummies(df['SEX'],prefix='IS_SEX')
display(df)
最终变换前后的结果如下:
# 原始数据
SEX
0 MALE
1 FEMALE
2 NaN
3 FEMALE
4 FEMALE
5 NaN
6 MALE
# 独热向量编码后
IS_SEX_FEMALE IS_SEX_MALE IS_SEX_NA
0 0 1 0
1 1 0 0
2 0 0 1
3 1 0 0
4 1 0 0
5 0 0 1
下面我们对’sex’, ‘class’, ‘pclass’, ‘embarked’, ‘who’, ‘family_size’, 'age_bin’这些字段都进行独热向量编码。
pd.get_dummies(df_titanic, columns=['sex', 'class', 'pclass', 'embarked', 'who', 'family_size', 'age_bin'],drop_first=True)

当然,我们也可以借助 SKLearn(查看ShowMeAI教程 SKLearn 最全应用指南 和 AI 建模工具速查 | Scikit-learn 使用指南 详细学习),进行独热向量编码实现:
import numpy as np
from sklearn.preprocessing import OneHotEncoder
# 非负整数表示的标签列表
labels = [0,1,0,2]
# 行向量转列向量
labels = np.array(labels).reshape(len(labels), -1)
# 独热向量编码
enc = OneHotEncoder()
enc.fit(labels)
targets = enc.transform(labels).toarray()
# 如果不加 toarray() 的话,输出的是稀疏的存储格式,即索引加值的形式,也可以通过参数指定 sparse = False 来达到同样的效果
输出结果如下:
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 1., 0., 0.],
[ 0., 0., 1.]])
(3) 标签二值化(LabelBinarizer)
功能与 OneHotEncoder 一样,但是 OneHotEncoder 只能对数值型变量二值化,无法直接对字符串型的类别变量编码,而 LabelBinarizer 可以直接对字符型变量二值化。
示例代码如下:
from sklearn.preprocessing import LabelBinarizer
lb=LabelBinarizer()
labelList=['yes', 'no', 'no', 'yes','no2']
# 将标签矩阵二值化
dummY=lb.fit_transform(labelList)
print("dummY:",dummY)
# 逆过程
yesORno=lb.inverse_transform(dummY)
print("yesOrno:",yesORno)
输出如下:
dummY: [[0 0 1]
[1 0 0]
[1 0 0]
[0 0 1]
[0 1 0]]
yesOrno: ['yes' 'no' 'no' 'yes' 'no2']
4.7 降维
在实际的机器学习项目中,我们可能还会做降维处理,主要因为数据存在以下几个问题:
- 数据的多重共线性:特征属性之间存在着相互关联关系。多重共线性会导致解的空间不稳定, 从而导致模型的泛化能力弱。
- 高纬空间样本具有稀疏性,导致模型比较难找到数据特征。
- 过多的变量会妨碍模型查找规律。
- 仅仅考虑单个变量对于目标属性的影响可能忽略变量之间的潜在关系。

通过特征降维希望达到的目的:
- 减少特征属性的个数
- 确保特征属性之间是相互独立的
常用的降维方法有:
- PCA
- SVD
- LDA
- T-sne 等非线性降维
这里降维的讲解,我们给大家基于 iris 数据集讲解:
from sklearn import datasets
iris_data = datasets.load_iris()
X = iris_data.data
y = iris_data.target
def draw_result(X, y):
plt.figure()
# 提取 Iris-setosa
setosa = X[y == 0]
# 绘制点:参数 1 x 向量,y 向量
plt.scatter(setosa[:, 0], setosa[:, 1], color="red", label="Iris-setosa")
versicolor = X[y == 1]
plt.scatter(versicolor[:, 0], versicolor[:, 1], color="orange", label="Iris-versicolor")
virginica = X[y == 2]
plt.scatter(virginica[:, 0], virginica[:, 1], color="blue", label="Iris-virginica")
plt.legend()
plt.show()
draw_result(X, y)

(1) PCA(Principal Component Analysis)
关于 PCA 主成分分析降维算法,大家可以查阅ShowMeAI文章 图解机器学习 | 降维算法详解 进行详细学习。
PCA 降维的参考代码实现如下:
import numpy as np
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
newX = pca.fit_transform(X)
draw_result(newX, y)

(2) SVD(Singular Value Decomposition)
SVD 方法的主要步骤如下:
A T A = ( U Σ V T ) T U Σ V T = V Σ T U T U Σ V T = V Σ T Σ V T = V Σ 2 V T A^{T} A=\left(U \Sigma V{T}\right) U \Sigma V^{T}=V \Sigma^{T} U^{T} U \Sigma V^{T}=V \Sigma^{T} \Sigma V^{T}=V \Sigma^{2} V^{T} ATA=(UΣVT)TUΣVT=VΣTUTUΣVT=VΣTΣVT=VΣ2VT
所以 V V V 是 A T A A^{T} A ATA 特征值分解的特征向量按列组成的正交矩阵, Σ 2 \Sigma^{2} Σ2 是 A T A A^{T} A ATA 特征值组成的对角矩阵,也可以看出 $A_{m \times n} $ 的奇异值 σ i \sigma_{i} σi 是 A T A A^{T} A ATA 特征值 λ i \lambda_{i} λi 的平方根。
σ i = λ i \sigma_{i}=\sqrt{\lambda_{i}} σi=λi
假如 A T A A^{T} A ATA 的特征向量为 v i v_{i} vi, U U U 中对应的 u i u_{i} ui 则可以由下式求出:
u i = A v i σ i u_{i}=\frac{A v_{i}}{\sigma_{i}} ui=σiAvi
也即奇异值分解的关键在于对 A T A A^{T} A ATA 进行特征值分解。
对应的代码参考实现如下:
from sklearn.decomposition import TruncatedSVD
iris_2d = TruncatedSVD(2).fit_transform(X)
draw_result(iris_2d, y)

PCA vs SVD
PCA 求解关键在于求解协方差矩阵 C = 1 m X X T C=\frac{1}{m} X X^{T} C=m1XXT 的特征值分解。
SVD 关键在于 A T A A^{T} A ATA 的特征值分解。
很明显二者所解决的问题非常相似,都是对一个实对称矩阵进行特征值分解,如果取:
A = X T m A=\frac{X^{T}}{\sqrt{m}} A=m XT
则有:
A T A = ( X T m ) T X T m = 1 m X X T A^{T} A=\left(\frac{X{T}}{\sqrt{m}}\right) \frac{X^{T}}{\sqrt{m}}=\frac{1}{m} X X^{T} ATA=(m XT)Tm XT=m1XXT
此时 SVD 与 PCA 等价,所以 PCA 问题可以转化为 SVD 问题求解。
(3) LDA(Linear Discriminant Analysis)
是有监督的降维,通过最小化类内离散度与最大化类间离散度来获得最优特征子集。

上图解读:LD1 通过线性判定,可以很好的将呈正态分布的两个类分开。LD2 的线性判定保持了数据集的较大方差,但 LD2 无法提供关于类别的信息,因此 LD2 不是一个好的线性判定。
对应的降维参考实现代码如下:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA(n_components=2)
iris_2d = lda.fit_transform(X, y)
draw_result(iris_2d, y)

LDA vs PCA
PCA 试图寻找到方差最大的正交的主成分分量轴 LDA 发现可以最优化分类的特征子空间 LDA 和 PCA 都是可用于降低数据集维度的线性转换技巧 PCA 是无监督算法 LDA 是监督算法 LDA 是一种更优越的用于分类的特征提取技术
(4) T-SNE
T-SNE(t-distributed stochastic neighbor embedding)是一种非线性降维方法,参考的代码实现如下:
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2)
iris_2d = tsne.fit_transform(X)
draw_result(iris_2d, y)

5.特征选择
特征选择是在建模过程中经常会用到的一个处理,也有重要意义:
- 特征冗余,部分特征相关度太高,消耗计算资源
- 存在噪声,对模型结果有负面影响
- 部分特征容易引起过拟合

总体来说,进行特征选择有 2 个主要考虑方向:
- 特征发散程度:如果一个特征不发散,例如方差接近于 0 0 0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
- 特征与目标的相关性:特征与目标相关性高,越应当被保留,这点大家也比较容易理解。
对特征选择的方法进行归类,又大体可以归纳为下述 3 种:
- Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数来选择特征。
- Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征或者排除若干特征。
- Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于 Filter 方法,但是是通过训练来确定特征的优劣。我们使用 SKLearn 中的
feature_selection库来进行特征选择。
5.1 过滤式 Filter
(1) 方差过滤
这是通过特征本身的方差来筛选特征的类。
比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。
我们会剔除掉方差非常小的字段特征,参考代码实现如下:
from sklearn.feature_selection import VarianceThreshold
variancethreshold = VarianceThreshold() #实例化,默认方差为 0.方差<=0 的过滤掉
df_titanic_numerical = df_titanic[['age','sibsp','parch','fare','family_size']]
X_var = variancethreshold.fit_transform(df_titanic_numerical) #获取删除不合格特征后的新特征矩阵
del_list = df_titanic_numerical.columns[variancethreshold.get_support()==0].to_list() #获得删除
(2) 卡方过滤
卡方检验,专用于分类算法,捕捉相关性,追求 p 小于显著性水平的特征。卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。

p 值和取到这一个统计量的概率取值其实是正相关的: p p p 值越大,取到这个统计量的概率就越大,即越合理; p p p 值越小,取到这个统计量的概率就越小,即越不合理,此时应该拒绝原假设,接收备择假设。
如下为卡方过滤的参考代码示例:
df_titanic_categorical = df_titanic[['sex', 'class', 'embarked', 'who', 'age_bin','adult_male','alone','fare_bin']]
df_titanic_numerical = df_titanic[['age','sibsp','parch','fare','family_size','pclass']]
df_titanic_categorical_one_hot = pd.get_dummies(df_titanic_categorical, columns=['sex', 'class', 'embarked', 'who', 'age_bin','adult_male','alone','fare_bin'], drop_first=True)
df_titanic_combined = pd.concat([df_titanic_numerical,df_titanic_categorical_one_hot],axis=1)
y = df_titanic['survived']
X = df_titanic_combined.iloc[:,1:]
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
chi_value, p_value = chi2(X,y)
#根据 p 值,得出 k 值
k = chi_value.shape[0] - (p_value > 0.05).sum() #要保留的特征的数量 14
#根据卡方值,选择前几特征,筛选后特征
X_chi = SelectKBest(chi2, k=14).fit_transform(X, y)
(3) F 检验
F F F 检验捕捉线性相关性,要求数据服从正态分布,追求 P P P 值小于显著性水平特征。
其特征选择的参考代码如下:
from sklearn.feature_selection import f_classif
f_value, p_value = f_classif(X,y)
#根据 p 值,得出 k 值
k = f_value.shape[0] - (p_value > 0.05).sum()
#筛选后特征
X_classif = SelectKBest(f_classif, k=14).fit_transform(X, y)
(4) 互信息法
互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。
其特征选择的参考代码如下:
from sklearn.feature_selection import mutual_info_classif as MIC
#互信息法
mic_result = MIC(X,y) #互信息量估计
k = mic_result.shape[0] - sum(mic_result <= 0) #16
X_mic = SelectKBest(MIC, k=16).fit_transform(X, y)
5.2 包裹式 Wrapper
(1) 递归特征删除法
递归消除删除法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。使用feature_selection 库的 RFE 类来选择特征的代码如下:
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
#递归特征消除法,返回特征选择后的数据
#参数 estimator 为基模型
#参数 n_features_to_select 为选择的特征个数
X_ref = RFE(estimator=LogisticRegression(), n_features_to_select=10).fit_transform(X, y)
(2) 特征重要性评估
我们基于一些模型(如各类树模型)可以得到特征重要度,进而进行筛选
from sklearn.ensemble import ExtraTreesClassifier
# 建模与获取特征重要度
model = ExtraTreesClassifier()
model.fit(X, y)
print(model.feature_importances_)
# 特征重要度排序
feature=list(zip(X.columns,model.feature_importances_))
feature=pd.DataFrame(feature,columns=['feature','importances'])
feature.sort_values(by='importances',ascending=False).head(20)
(3) 排列重要性评估
我们还有一类方法可以评估特征重要度,进而进行筛选,叫作排列重要度。
原理:在训练机器学习模型之后计算置换重要性。这种方法在向模型提出假设,如果在保留目标和所有其他列的同时随机打乱一列验证集特征数据,对预测机器学习模型的准确性的影响程度。对于一个具有高度重要性的特征,random-reshuffle 会对机器学习模型预测的准确性造成更大的损害。
优点:快速计算;易于使用和理解;特征重要性度量的属性;追求特征稳定性。
参考代码实现如下:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import eli5
from eli5.sklearn import PermutationImportance
my_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)
perm = PermutationImportance(my_model, random_state=1).fit(val_X, val_y)
eli5.show_weights(perm, feature_names = val_X.columns.tolist())

5.3 嵌入式 Embedded
(1) 基于惩罚项的特征选择法
使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。
使用feature_selection库的SelectFromModel类结合带 L1 惩罚项的逻辑回归模型,来选择特征的代码如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
#带 L1 和 L2 惩罚项的逻辑回归作为基模型的特征选择,这个设置带 L1 惩罚项的逻辑回归作为基模型的特征选择
lr = LogisticRegression(solver='liblinear',penalty="l1", C=0.1)
X_sfm = SelectFromModel(lr).fit_transform(X, y)
X_sfm.shape
(891, 7
使用 feature_selection 库的 SelectFromModel 类结合 SVM 模型,来选择特征的代码如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.svm import LinearSVC
lsvc = LinearSVC(C=0.01,penalty='l1',dual=False).fit(X, y)
model = SelectFromModel(lsvc,prefit=True)
X_sfm_svm = model.transform(X)
X_sfm_svm.shape
(891, 7
(2) 基于树模型
树模型中 GBDT 也可用来作为基模型进行特征选择,使用 feature_selection 库的 SelectFromModel 类结合 GBDT 模型,来选择特征的代码如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT 作为基模型的特征选择
gbdt = GradientBoostingClassifier()
X_sfm_gbdt = SelectFromModel(gbdt).fit_transform(X, y)
5.4 特征选择总结
关于特征选择,做一个经验总结,如下:
- ① 类别型特征变量,那么可以从 SelectKBest 开始,用卡方或者基于树的选择器来选择变量;
- ② 定量特征变量,可以直接用线性模型和基于相关性的选择器来选择变量;
- ③ 二分类问题,可以考虑使用 SelectFromModel 和 SVC;
- ④ 特征选择前,要充分了解数据,一般需要做探索性数据分析 EDA。
6.特征工程实战建议
最后,ShowMeAI结合实际工业应用经验,总结一些特征工程要点,如下:

6.1 数据理解
构建特征的有效性,和业务及数据分布强相关,因此建议在此步骤之前做 EDA 探索性数据分析来充分理解数据(可以参考ShowMeAI文章 Python 机器学习综合项目-电商销量预估 和 Python 机器学习综合项目-电商销量预估<进阶> 了解 EDA 的基本过程和方法)。
6.2 数据预处理
我们可能会做的一些数据预处理与特征处理如下:
连续特征离散化
- 本质是限制浮点数特征的精度,异常数据有很强的鲁棒性,模型也会更稳定。
- 树模型不需要做
数值截断
- 把特征值的取值限制在一定范围内(对异常剔除有帮助)
- 可以用 pandas dataframe 的
.clip(low,upper)方法
6.3 数据清洗
结合业务场景和数据分布,进行合理的缺失值、异常值处理。
6.4 特征构建与变换
建议不要上来就做 PCA 或 LDA 降维,最好先构建特征并对特征做筛选。

线性组合(linear combination)
- 适用于决策树以及基于决策树的 ensemble(如 gradient boosting,random forest),因为常见的 axis-aligned split function 不擅长捕获不同特征之间的相关性;
- 不适用于 SVM、线性回归、神经网络等。
类别特征与数值特征的组合
- 用 N1 和 N2 表示数值特征,用 C1 和 C2 表示类别特征,利用 pandas 的 groupby 操作,可以创造出以下几种有意义的新特征:(其中,C2 还可以是离散化了的 N1)
median(N1)_by(C1) 中位数
mean(N1)_by(C1) 算术平均数
mode(N1)_by(C1) 众数
min(N1)_by(C1) 最小值
max(N1)_by(C1) 最大值
std(N1)_by(C1) 标准差
var(N1)_by(C1) 方差
freq(C2)_by(C1) 频数
统计特征+线性组合
- 统计特征可以和线性组合等基础特征工程方法结合(仅用于决策树),可以得到更多有意义的特征,如:
N1 - median(N1)_by(C1)
N1 - mean(N1)_by(C1)
基于树模型创造新特征
- 在决策树系列算法中(例决策树、gbdt、随机森林,具体可以查看ShowMeAI教程 图解机器学习算法:从入门到精通系列教程 详细学习理解),每一个样本都会被映射到决策树的叶子上。
- 我们可以把样本经过每一棵决策树映射后的 index(自然数)或 one-hot-encoding-vector (哑编码得到的稀疏矢量)作为一项新的特征,加入到模型中。
在 Scikit-Learn 和 XGBoost 里,可以基于
apply()以及decision_path()等方法实现。
6.5 模型
我们在不同类型的模型里,也会考虑不同的特征工程方法

树模型
- 对特征数值幅度不敏感,可以不进行无量纲化和统计变换处理;
- 数模型特征依赖于样本距离来进行学习,可以不进行类别特征编码(但字符型特征不能直接作为输入,所以需要至少要进行标签编码)。
- LightGBM 和 XGBoost 都能将缺失值作为数据的一部分进行学习,所以不需要处理缺失值。其他情况需要填充缺失。
依赖样本距离的模型
- 如线性回归、SVM、深度学习等属于这一类。
- 对于数值型特征需要进行无量纲化处理。
- 对于一些长尾分布的数据特征,可以做统计变换,使得模型能更好优化。
- 对于线性模型,特征分箱可以提升模型表达能力。
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模
![]()
机器学习实战 | 自动化特征工程工具 Featuretools 应用

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/209
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
引言
在ShowMeAI的文章 机器学习特征工程最全解读 里,我们给大家详细介绍了特征工程的操作,但我们实际上有很多工具可以辅助我们更快捷地完成特征工程,在本篇内容中,ShowMeAI给大家介绍 Featuretools 这个 Python 自动化特征工程的工具库。我们会借助于 BigMart Sales 数据集来演示自动化特征工程的相关应用。

1.机器学习与特征
在机器学习的背景下,特征是用来解释现象发生的单个特性或一组特性。当这些特性转换为某种可度量的形式时,它们被称为特征。

2.特征工程介绍
特征工程(feature engineering):利用领域知识和现有数据,创造出新的特征,用于机器学习算法;可以手动(manual)或自动(automated)。

数据与特征工程决定了模型的上限,改进算法只不过是逼近这个上限而已。
3.特征工程意义


4.自动化特征工程

上左图显示了 20 世纪初一群人正在组装汽车,上右图显示了当今一群机器人在做同样的工作。自动化任何流程都可以使其变得更加高效和经济。同样,特征工程也是如此。而且,在机器学习中,常用特征的特征工程已经实现自动化。
我们有一个很好的工具可以用来帮忙完成自动化特征工程的过程,这个 Python 工具库的名称叫 Featuretools。
5.Featuretools 简介
Featuretools 是一个 Python 自动化特征工程的工具库。它可以帮助大家快速构建丰富的数据特征,而把更多的时间聚焦于构建机器学习模型的其他方面。
要学会使用 Featuretools,我们要先了解它的三个主要组件:

实体(Entities)
- 一个 Entity 可以视作是一个 Pandas 的数据框的表示,多个实体的集合称为 Entityset。
关系(relationship)
- 关系就是表之间的关联键的定义。
特征算子(Feature primitives)
- DFS 通过将特征算子应用于 Entityset 的实体关系来构造新特征。算子就是一些特征工程的函数,例如 groupby、mean、max、min 等等。
Featuretools 实际上就是提供了一个框架让我们可以方便快速的通过简约的代码来实现单表的转换操作和多表的跨表连接操作,下面我们借助于 BigMart Sales 数据集实践问题中来具体讲解 Featuretools 工具。
6.Featuretools 实践
这里的案例场景 BigMart Sales 要解决的是电商领域的销量预估问题,我们希望构建模型来估算特定门店中每种商品的销售额,这将有助于 BigMart 的决策者找出每一个产品或门店的重要属性,这对提高整体销售起着关键性作用。请注意,在给定的数据集中,有跨 10 个门店的 1559 种商品。
数据集如下:
链接:https://pan.baidu.com/s/1qjJZjY56MnHBvmUQEMjq9g
提取码:show
下表给出了数据字段说明:
| 变量 | 描述 |
|---|---|
| Item_Identifier | 商品编号 |
| Item_Weight | 商品重量 |
| Item_Fat_Content | 是否是低脂商品 |
| Item_Visibility | 该商品展示区域占门店中所有商品展示区域的比例 |
| Item_Type | 商品所属分类 |
| Item_MRP | 商品最高售价 |
| Outlet_Identifier | 门店编号 |
| Outlet_Establishment_Year | 门店建立年份 |
| Outlet_Size | 门店占地面积 |
| Outlet_Location_Type | 门店所在城市类型 |
| Outlet_Type | 门店类型(杂货店或超市) |
| Item_Outlet_Sales | 门店商品销售额 (即需要预测的输出变量) |
6.1 Featuretools 安装
大家可以在命令行使用 pip 轻松安装 Featuretools。
pip install featuretools
6.2 导入依赖工具库及数据
import featuretools as ft
import numpy as np
import pandas as pd
train = pd.read_csv("Train.csv")
test = pd.read_csv("test.csv")
6.3 数据准备
我们先从数据中提取出目标字段和特征字段,如下:
# saving identifiers
test_Item_Identifier = test['Item_Identifier']
test_Outlet_Identifier = test['Outlet_Identifier']
sales = train['Item_Outlet_Sales']
train.drop(['Item_Outlet_Sales'], axis=1, inplace=True)
接着,我们合并训练集和测试集,以完成统一而一致的数据处理变换。
combi = train.append(test, ignore_index=True)
我们查看一下数据集的缺失值情况。
combi.isnull().sum()

我们发现字段Item_Weight和Outlet_size中有非常多的缺失值,先做一个快速处理:
# 缺失值处理
combi['Item_Weight'].fillna(combi['Item_Weight'].mean(), inplace = True)
combi['Outlet_Size'].fillna("missing", inplace = True)
6.4 数据预处理
我们只做一点简单的数据预处理,这样后续可以更充分直观地展示 Featuretools 的功能。
combi['Item_Fat_Content'].value_counts()

我们发现Item_Fat_Content只包含两个类别:「低脂肪」和「常规」(虽然在字段取值上有多种,但其只是格式差异),这里我们对其进行二值化变换。
# 二值编码
fat_content_dict = {'Low Fat':0, 'Regular':1, 'LF':0, 'reg':1, 'low fat':0}
combi['Item_Fat_Content'] = combi['Item_Fat_Content'].replace(fat_content_dict, regex=True)
6.5 Featuretools 特征工程
下面我们使用 Featuretools 来实现自动化特征工程。首先我们将「商品」和「门店」信息组合,构建一个数据唯一 ID。
combi['id'] = combi['Item_Identifier'] + combi['Outlet_Identifier']
combi.drop(['Item_Identifier'], axis=1, inplace=True)
因为不再需要特征 Item_Identifier,我们把它删除了。我们保留了特征 Outlet_Identifier,稍后会使用到它。
接下来我们创建一个特征EntitySet,它是一种包含多个数据框及其之间关系的结构。
# 构建实体集合 es
es = ft.EntitySet(id = 'sales')
# 添加 dataframe 数据
es.add_dataframe(dataframe_name = 'bigmart', dataframe = combi, index = 'id')
下面我们将使用深度特征综合(Deep Feature Synthesis)自动创建新特征。
trans_primitives=['add_numeric', 'subtract_numeric', 'multiply_numeric', 'divide_numeric'] # 2 列相加减乘除来生成新特征
agg_primitives=['sum', 'median','mean']
feature_matrix, feature_names = ft.dfs(entityset=es,
target_dataframe_name = 'bigmart',
max_depth = 1,
verbose = 1,
agg_primitives=agg_primitives,
trans_primitives=trans_primitives,
n_jobs = 8)

上述代码中:
max_depth控制由叠加特征基元方式生成的特征的复杂性。agg_primitives是定义了一些统计聚合方式。trans_primitives定义了变换计算算子。n_jobs设定了多核并行特征计算的核数。
通过上述操作,Featuretools 就自行构造了许多新特征。
让我们来看看这些新构造的特征:
feature_matrix.columns

你会发现 DFS 快速构建出了非常多新特征。比我们手动操作构建特征要高效得多!
我们查看一下feature_matrix的前几行。
feature_matrix.head()

我们对这个 Dataframe 做一点小调整,我们根据 combi 数据框中的 id 变量对其进行排序。
feature_matrix = feature_matrix.reindex(index=combi['id'])
feature_matrix = feature_matrix.reset_index()
6.6 特征解释
我们还可以通过以下代码来对其构建出来的特征做解释,比如我们要解释第 20 个特征是如何得到的。
ft.graph_feature(feature_names[20])

6.7 构建模型
下面我们就可以用构建出来的特征来建模啦,预测 Item_Outlet_Sales。由于最终的数据(feature_matrix)里具有许多类别特征,我们这里使用 LightGBM 模型。它可以直接使用类别特征,并且本质上是可扩展的。
你可以阅读ShowMeAI的文章 图解机器学习 | LightGBM 模型详解 和 LightGBM 建模应用详解 了解 LightGBM 模型的原理和应用方法。
import lightgbm as lgb
import pandas as pd
CatBoost 要求所有类别变量都采用字符串格式。因此,我们首先将数据中的类别变量转换为字符串:
categorical_features = np.where(feature_matrix.dtypes == 'object')[0]
for i in categorical_features:
feature_matrix.iloc[:,i] = feature_matrix.iloc[:,i].astype('str')
然后重新把 feature_matrix 拆回训练集和测试集。
feature_matrix.drop(['id'], axis=1, inplace=True)
train = feature_matrix[:8523]
test = feature_matrix[8523:]
# removing uneccesary variables
train.drop(['Outlet_Identifier'], axis=1, inplace=True)
test.drop(['Outlet_Identifier'], axis=1, inplace=True)
将训练集拆成训练和验证两部分,以便在本地测试算法的性能。
from sklearn.model_selection import train_test_split
# splitting train data into training and validation set
xtrain, xvalid, ytrain, yvalid = train_test_split(train, sales, test_size=0.25, random_state=11)
最后,训练模型。采用 RMSE(Root Mean Squared Error,均方根误差) 作为衡量指标。
# 初始化 LGBMRegressor 回归器
model_lgb = lgb.LGBMRegressor(iterations=5000, learning_rate=0.05, depth=6, eval_metric='RMSE', random_seed=7)
# 训练模型
model_lgb.fit(xtrain, ytrain, eval_set=[(xvalid, yvalid)], early_stopping_rounds=1000)

from sklearn.metrics import mean_squared_error
np.sqrt(mean_squared_error(model_lgb.predict(xvalid), yvalid))
验证数据集的 RMSE 得分是
。
在没有任何特征工程的情况下,验证集的得分为
。 因此,Featuretools 构造的特征不仅仅是随机特征,而且还非常有价值的。最重要的是,它使特征工程节省了大量时间。
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模
机器学习实战 | AutoML 自动化机器学习建模

作者:韩信子@ShowMeAI
教程地址:www.showmeai.tech/tutorials/41
本文地址:www.showmeai.tech/article-detail/210
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
1.AutoML 与自动化机器学习
在前序系列文章中大家跟着ShowMeAI一起学习了如何构建机器学习应用。我们构建一个机器学习模型解决方案 baseline 很容易,但模型选择和泛化性能优化是一项艰巨的任务。选择合适的模型并是一个需要高计算成本、时间和精力的过程。
针对上述问题就提出了AutoML,AutoML(Automated machine learning)是自动化构建端到端机器学习流程,解决实际场景问题的过程。

在本篇内容中我们将介绍到微软开发的高效轻量级自动化机器学习框架 FLAML(A Fast and Lightweight AutoML Library)。
2.FLAML 介绍
2.1 FLAML 特点
官方网站对 FLAML 的特点总结如下:
- 对于分类和回归等常见的机器学习任务,FLAML 可以在消耗尽量少的资源前提下,快速找到高质量模型。它支持经典机器学习模型和深度神经网络。
- 它很容易定制或扩展。用户可以有很灵活的调整与定制模式:
- 最小定制(设定计算资源限制)
- 中等定制(例如设定 scikit-learn 学习器、搜索空间和度量标准)
- 完全定制(自定义训练和评估代码)。
- 它支持快速且低消耗的自动调优,能够处理大型搜索空间。 FLAML 由 Microsoft Research 发明的新的高效益超参数优化和学习器选择方法支撑。
2.2 安装方法
我们可以通过 pip 轻松安装上 FLAML
pip install flaml
有一些可选的安装选项,如下:
(1) Notebook 示例支持
如果大家要跑官方的 notebook 代码示例,安装时添加[notebook]选项:
pip install flaml[notebook]
(2) 更多模型学习器支持
- 如果我们希望 flaml 支持 catboost 模型,安装时添加[catboost]选项
pip install flaml[catboost]
- 如果我们希望 flaml 支持 vowpal wabbit ,安装时添加[vw]选项
pip install flaml[vw]
- 如果我们希望 flaml 支持时间序列预估器 prophet 和 statsmodels,安装时可以添加[forecast]
pip install flaml[forecast]
(3) 分布式调优支持
- ray
pip install flaml[ray]
- nni
pip install flaml[nni]
- blendsearch
pip install flaml[blendsearch]
3.FLAML 使用示例
3.1 全自动模式
下面我们用一个场景数据案例(二分类)来演示 FLAML 工具库的全自动模式。(大家可以在 jupyter notebook 中运行下列的代码,关于 IDE 与环境配置大家可以参考ShowMeAI文章 图解 python | 安装与环境设置)。
!pip install flaml[notebook]
(1) 加载数据和预处理
我们从 OpenML 下载航空公司数据集 Airlines dataset。 这个数据集的建模任务是在给定预定起飞信息的情况下预测给定航班是否会延误。
from flaml.data import load_openml_dataset
X_train, X_test, y_train, y_test = load_openml_dataset(dataset_id=1169, data_dir='./')

从运行结果可以看到训练集测试集及标签的维度信息。
(2) 运行 FLAML 全自动模式
下面我们直接运行 FLAML automl 全自动模式,实际在运行配置中,我们可以指定 任务类型、时间预算、误差度量、学习者列表、是否下采样、重采样策略类型等。 如果不作任何设定的话,所有这些参数都会使用默认值(例如,默认分类器是 [lgbm, xgboost, xgb_limitdepth, catboost, rf, extra_tree, lrl1])。
# 导入工具库并初始化 AutoML 对象
from flaml import AutoML
automl = AutoML()
# 参数设定
settings = {
"time_budget": 240, # 总时间上限(单位秒)
"metric": 'accuracy', # 候选可以是: 'r2', 'rmse', 'mae', 'mse', 'accuracy', 'roc_auc', 'roc_auc_ovr', 'roc_auc_ovo', 'log_loss', 'mape', 'f1', 'ap', 'ndcg', 'micro_f1', 'macro_f1'
"task": 'classification', # 任务类型
"log_file_name": 'airlines_experiment.log', # flaml 日志文件
"seed": 7654321, # 随机种子
}
# 运行自动化机器学习
automl.fit(X_train=X_train, y_train=y_train, **settings)


从上述运行结果可以看出,自动机器学习过程,对[lgbm, xgboost, xgb_limitdepth, catboost, rf, extra_tree, lrl1]这些候选模型进行了实验,并运行出了对应的结果。
(3) 最优模型与评估结果

print('Best ML leaner:', automl.best_estimator)
print('Best hyperparmeter config:', automl.best_config)
print('Best accuracy on validation data: {0:.4g}'.format(1-automl.best_loss))
print('Training duration of best run: {0:.4g} s'.format(automl.best_config_train_time))
运行结果如下
Best ML leaner: lgbm
Best hyperparmeter config: {'n_estimators': 1071, 'num_leaves': 25, 'min_child_samples': 36, 'learning_rate': 0.10320258241974468, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.08547376339713011, 'FLAML_sample_size': 364083}
Best accuracy on validation data: 0.6696
Training duration of best run: 9.274 s
可以通过运行完毕的 automl 对象属性,取出对应的「最优模型」、「最佳模型配置」、「评估准则结果」等信息。在这里最优的模型是 1071 颗树构建而成的一个 LightGBM 模型。
更进一步,我们可以通过下面的代码,取出最优模型,并用它对测试集进行预测。
# 最佳模型
automl.model.estimator
运行结果如下
LGBMClassifier(learning_rate=0.10320258241974468, max_bin=1023,
min_child_samples=36, n_estimators=1071, num_leaves=25,
reg_alpha=0.0009765625, reg_lambda=0.08547376339713011,
verbose=-1)
(4) 模型存储与加载
# 模型存储与持久化
import pickle
with open('automl.pkl', 'wb') as f:
pickle.dump(automl, f, pickle.HIGHEST_PROTOCOL)
# 模型加载
with open('automl.pkl', 'rb') as f:
automl = pickle.load(f)
# 对测试集进行预估
y_pred = automl.predict(X_test)
print('Predicted labels', y_pred)
print('True labels', y_test)
y_pred_proba = automl.predict_proba(X_test)[:,1]
运行结果如下:
Predicted labels ['1' '0' '1' ... '1' '0' '0']
True labels 118331 0
328182 0
335454 0
520591 1
344651 0
..
367080 0
203510 1
254894 0
296512 1
362444 0
Name: Delay, Length: 134846, dtype: category
Categories (2, object): ['0' < '1']
可以看到,automl 得到的最佳模型,对测试集预估的方式,和自己建模得到的模型是一样的。
# 测试集效果评估
from flaml.ml import sklearn_metric_loss_score
print('accuracy', '=', 1 - sklearn_metric_loss_score('accuracy', y_pred, y_test))
print('roc_auc', '=', 1 - sklearn_metric_loss_score('roc_auc', y_pred_proba, y_test))
print('log_loss', '=', sklearn_metric_loss_score('log_loss', y_pred_proba, y_test))
评估结果如下:
accuracy = 0.6720332824110467
roc_auc = 0.7253276908529442
log_loss = 0.6034449031876942
(5) 查看日志历史详情
我们可以通过如下代码,查看 automl 对各个模型实验的结果详细数据。
from flaml.data import get_output_from_log
time_history, best_valid_loss_history, valid_loss_history, config_history, metric_history = \
get_output_from_log(filename=settings['log_file_name'], time_budget=240)
for config in config_history:
print(config)
结果如下
{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 4, 'num_leaves': 4, 'min_child_samples': 20, 'learning_rate': 0.09999999999999995, 'log_max_bin': 8, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 1.0, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 4, 'num_leaves': 4, 'min_child_samples': 20, 'learning_rate': 0.09999999999999995, 'log_max_bin': 8, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 1.0, 'FLAML_sample_size': 10000}}
{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 4, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242716, 'reg_lambda': 7.624911621832711, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 4, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242716, 'reg_lambda': 7.624911621832711, 'FLAML_sample_size': 10000}}
{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 4, 'num_leaves': 25, 'min_child_samples': 12, 'learning_rate': 0.5082200481556802, 'log_max_bin': 8, 'colsample_bytree': 0.9696263001275751, 'reg_alpha': 0.0028107036379524425, 'reg_lambda': 3.716898117989413, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 4, 'num_leaves': 25, 'min_child_samples': 12, 'learning_rate': 0.5082200481556802, 'log_max_bin': 8, 'colsample_bytree': 0.9696263001275751, 'reg_alpha': 0.0028107036379524425, 'reg_lambda': 3.716898117989413, 'FLAML_sample_size': 10000}}
{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 23, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242718, 'reg_lambda': 7.624911621832699, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 23, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242718, 'reg_lambda': 7.624911621832699, 'FLAML_sample_size': 10000}}
{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 10000}}
{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 40000}}
{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 40000}}
{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 364083}}
{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 547, 'num_leaves': 46, 'min_child_samples': 60, 'learning_rate': 0.281323306091088, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001643352694266288, 'reg_lambda': 0.14719738747481906, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 547, 'num_leaves': 46, 'min_child_samples': 60, 'learning_rate': 0.281323306091088, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001643352694266288, 'reg_lambda': 0.14719738747481906, 'FLAML_sample_size': 364083}}
{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 1071, 'num_leaves': 25, 'min_child_samples': 36, 'learning_rate': 0.10320258241974468, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.08547376339713011, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 1071, 'num_leaves': 25, 'min_child_samples': 36, 'learning_rate': 0.10320258241974468, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.08547376339713011, 'FLAML_sample_size': 364083}}
我们可以绘制出验证集的学习曲线,如下:
import matplotlib.pyplot as plt
import numpy as np
plt.title('Learning Curve')
plt.xlabel('Wall Clock Time (s)')
plt.ylabel('Validation Accuracy')
plt.scatter(time_history, 1 - np.array(valid_loss_history))
plt.step(time_history, 1 - np.array(best_valid_loss_history), where='post')
plt.show()

(6) 对比默认 XGBoost/LightGBM 实验结果
我们来对比一下全部使用默认参数的 XGBoost 模型在本数据集上的效果,代码如下
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
# 训练拟合
xgb = XGBClassifier()
cat_columns = X_train.select_dtypes(include=['category']).columns
X = X_train.copy()
X[cat_columns] = X[cat_columns].apply(lambda x: x.cat.codes)
xgb.fit(X, y_train)
lgbm = LGBMClassifier()
lgbm.fit(X_train, y_train)
# 测试集预估
X = X_test.copy()
X[cat_columns] = X[cat_columns].apply(lambda x: x.cat.codes)
y_pred_xgb = xgb.predict(X)
y_pred_lgbm = lgbm.predict(X_test)
# 评估效果
print('默认参数 xgboost accuracy', '=', 1 - sklearn_metric_loss_score('accuracy', y_pred_xgb, y_test))
print('默认参数 lgbm accuracy', '=', 1 - sklearn_metric_loss_score('accuracy', y_pred_lgbm, y_test))
print('flaml (4min) accuracy', '=', 1 - sklearn_metric_loss_score('accuracy', y_pred, y_test))
最终结果如下:
默认参数 xgboost accuracy = 0.6676060098186078
默认参数 lgbm accuracy = 0.6602346380315323
flaml (4min) accuracy = 0.6720332824110467
从对比结果中可以看出,flaml 自动机器学习调优的最佳模型,效果优于默认参数的 XGBoost 和 LightGBM 建模结果。
3.2 自定义学习器
除了完全自动化模式使用 FLAML 工具库,我们还可以对它的一些组件进行自定义,实现自定义调优。比如我们可以对「模型」「参数搜索空间」「候选学习器」「模型优化指标」等进行设置。
(1) 自定义模型
正则化贪心森林 (RGF) 是一种机器学习方法,目前未包含在 FLAML 中。 RGF 有许多调整参数,其中最关键的是:[max_leaf, n_iter, n_tree_search, opt_interval, min_samples_leaf]。 要运行自定义/新学习器,用户需要提供以下信息:
- 自定义/新学习器的实现
- 超参数名称和类型的列表
- 超参数的粗略范围(即上限/下限)
在下面的示例代码中,RGF 信息被包装在一个名为 MyRegularizedGreedyForest 的 python 类中。
from flaml.model import SKLearnEstimator
from flaml import tune
from flaml.data import CLASSIFICATION
class MyRegularizedGreedyForest(SKLearnEstimator):
def __init__(self, task='binary', **config):
'''Constructor
Args:
task: A string of the task type, one of
'binary', 'multi', 'regression'
config: A dictionary containing the hyperparameter names
and 'n_jobs' as keys. n_jobs is the number of parallel threads.
'''
super().__init__(task, **config)
'''task=binary or multi for classification task'''
if task in CLASSIFICATION:
from rgf.sklearn import RGFClassifier
self.estimator_class = RGFClassifier
else:
from rgf.sklearn import RGFRegressor
self.estimator_class = RGFRegressor
@classmethod
def search_space(cls, data_size, task):
'''[required method] search space
Returns:
A dictionary of the search space.
Each key is the name of a hyperparameter, and value is a dict with
its domain (required) and low_cost_init_value, init_value,
cat_hp_cost (if applicable).
e.g.,
{'domain': tune.randint(lower=1, upper=10), 'init_value': 1}.
'''
space = {
'max_leaf': {'domain': tune.lograndint(lower=4, upper=data_size[0]), 'init_value': 4, 'low_cost_init_value': 4},
'n_iter': {'domain': tune.lograndint(lower=1, upper=data_size[0]), 'init_value': 1, 'low_cost_init_value': 1},
'n_tree_search': {'domain': tune.lograndint(lower=1, upper=32768), 'init_value': 1, 'low_cost_init_value': 1},
'opt_interval': {'domain': tune.lograndint(lower=1, upper=10000), 'init_value': 100},
'learning_rate': {'domain': tune.loguniform(lower=0.01, upper=20.0)},
'min_samples_leaf': {'domain': tune.lograndint(lower=1, upper=20), 'init_value': 20},
}
return space
@classmethod
def size(cls, config):
'''[optional method] memory size of the estimator in bytes
Args:
config - the dict of the hyperparameter config
Returns:
A float of the memory size required by the estimator to train the
given config
'''
max_leaves = int(round(config['max_leaf']))
n_estimators = int(round(config['n_iter']))
return (max_leaves * 3 + (max_leaves - 1) * 4 + 1.0) * n_estimators * 8
@classmethod
def cost_relative2lgbm(cls):
'''[optional method] relative cost compared to lightgbm
'''
return 1.0
(2) 运行 FLAML 自定义模型 automl
将 RGF 添加到学习器列表后,我们通过调整 RGF 的超参数以及默认学习器来运行 automl。
automl = AutoML()
automl.add_learner(learner_name='RGF', learner_class=MyRegularizedGreedyForest)
# 添加配置
settings = {
"time_budget": 10, # total running time in seconds
"metric": 'accuracy',
"estimator_list": ['RGF', 'lgbm', 'rf', 'xgboost'], # list of ML learners
"task": 'classification', # task type
"log_file_name": 'airlines_experiment_custom_learner.log', # flaml log file
"log_training_metric": True, # whether to log training metric
}
automl.fit(X_train = X_train, y_train = y_train, **settings)
(3) 自定义优化指标
我们可以为模型自定义优化指标。 下面的示例代码中,我们合并训练损失和验证损失作为自定义优化指标,并对其进行优化,希望损失最小化。
def custom_metric(X_val, y_val, estimator, labels, X_train, y_train,
weight_val=None, weight_train=None, config=None,
groups_val=None, groups_train=None):
from sklearn.metrics import log_loss
import time
start = time.time()
y_pred = estimator.predict_proba(X_val)
pred_time = (time.time() - start) / len(X_val)
val_loss = log_loss(y_val, y_pred, labels=labels,
sample_weight=weight_val)
y_pred = estimator.predict_proba(X_train)
train_loss = log_loss(y_train, y_pred, labels=labels,
sample_weight=weight_train)
alpha = 0.5
return val_loss * (1 + alpha) - alpha * train_loss, {
"val_loss": val_loss, "train_loss": train_loss, "pred_time": pred_time
}
# two elements are returned:
# the first element is the metric to minimize as a float number,
# the second element is a dictionary of the metrics to log
automl = AutoML()
settings = {
"time_budget": 10, # total running time in seconds
"metric": custom_metric, # pass the custom metric funtion here
"task": 'classification', # task type
"log_file_name": 'airlines_experiment_custom_metric.log', # flaml log file
}
automl.fit(X_train = X_train, y_train = y_train, **settings)
3.3 sklearn 流水线调优
FLAML 可以配合 sklearn pipeline 进行模型自动化调优,我们这里依旧以航空公司数据集 Airlines dataset 案例为场景,对其用法做一个讲解。
(1) 加载数据集
# 数据集准备
from flaml.data import load_openml_dataset
X_train, X_test, y_train, y_test = load_openml_dataset(
dataset_id=1169, data_dir='./', random_state=1234, dataset_format='array')
(2) 构建建模流水线
import sklearn
from sklearn import set_config
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from flaml import AutoML
set_config(display='diagram')
imputer = SimpleImputer()
standardizer = StandardScaler()
automl = AutoML()
automl_pipeline = Pipeline([
("imputuer",imputer),
("standardizer", standardizer),
("automl", automl)
])
automl_pipeline
输出结果如下
Pipeline(steps=[('imputuer', SimpleImputer()),
('standardizer', StandardScaler()),
('automl', )])
SimpleImputerSimpleImputer()
StandardScalerStandardScaler()
AutoML
(3) 参数设定与 automl 拟合
# 设定
settings = {
"time_budget": 60, # 总时长约束
"metric": 'accuracy', # 可选: ['accuracy','roc_auc', 'roc_auc_ovr', 'roc_auc_ovo', 'f1','log_loss','mae','mse','r2']
"task": 'classification', # 任务类型
"estimator_list":['xgboost','catboost','lgbm'],
"log_file_name": 'airlines_experiment.log', # flaml 日志文件
}
# 拟合
automl_pipeline.fit(X_train, y_train,
automl__time_budget=settings['time_budget'],
automl__metric=settings['metric'],
automl__estimator_list=settings['estimator_list'],
automl__log_training_metric=True)
(4) 取出最优模型
# Get the automl object from the pipeline
automl = automl_pipeline.steps[2][1]
# Get the best config and best learner
print('Best ML leaner:', automl.best_estimator)
print('Best hyperparmeter config:', automl.best_config)
print('Best accuracy on validation data: {0:.4g}'.format(1-automl.best_loss))
print('Training duration of best run: {0:.4g} s'.format(automl.best_config_train_time))
automl.model
运行结果如下:
Best ML leaner: xgboost
Best hyperparmeter config: {'n_estimators': 63, 'max_leaves': 1797, 'min_child_weight': 0.07275175679381725, 'learning_rate': 0.06234183309508761, 'subsample': 0.9814772488195874, 'colsample_bylevel': 0.810466508891351, 'colsample_bytree': 0.8005378817953572, 'reg_alpha': 0.5768305704485758, 'reg_lambda': 6.867180836557797, 'FLAML_sample_size': 364083}
Best accuracy on validation data: 0.6721
Training duration of best run: 15.45 s
<flaml.model.XGBoostSklearnEstimator at 0x7f03a5eada00>
(5) 测试集评估与模型存储
import pickle
with open('automl.pkl', 'wb') as f:
pickle.dump(automl, f, pickle.HIGHEST_PROTOCOL)
# 测试集预估与效果评估
y_pred = automl_pipeline.predict(X_test)
print('Predicted labels', y_pred)
print('True labels', y_test)
y_pred_proba = automl_pipeline.predict_proba(X_test)[:,1]
print('Predicted probas ',y_pred_proba[:5])
运行结果如下
Predicted labels [0 1 1 ... 0 1 0]
True labels [0 0 0 ... 1 0 1]
Predicted probas [0.3764987 0.6126277 0.699604 0.27359942 0.25294745]
3.4 XGBoost 自动调优
这里我们简单给大家讲一下如何使用 FLAML 调优最常见的模型之一 XGBoost。
(1) 工具库导入与基本设定
# 导入工具库
from flaml import AutoML
automl = AutoML()
# 参数设定
settings = {
"time_budget": 120, # total running time in seconds
"metric": 'r2', # primary metrics for regression can be chosen from: ['mae','mse','r2','rmse','mape']
"estimator_list": ['xgboost'], # list of ML learners; we tune xgboost in this example
"task": 'regression', # task type
"log_file_name": 'houses_experiment.log', # flaml log file
}
(2) 自动化机器学习拟合
automl.fit(X_train=X_train, y_train=y_train, **settings)
(3) 最优模型与评估
我们可以输出最优模型配置及详细信息
# 最优模型
print('Best hyperparmeter config:', automl.best_config)
print('Best r2 on validation data: {0:.4g}'.format(1 - automl.best_loss))
print('Training duration of best run: {0:.4g} s'.format(automl.best_config_train_time))
运行结果:
Best hyperparmeter config: {'n_estimators': 776, 'max_leaves': 160, 'min_child_weight': 32.57408640781376, 'learning_rate': 0.034786853332414935, 'subsample': 0.9152991332236934, 'colsample_bylevel': 0.5656764254642628, 'colsample_bytree': 0.7313266091895249, 'reg_alpha': 0.005771390107656191, 'reg_lambda': 1.49126672786588}
Best r2 on validation data: 0.834
Training duration of best run: 9.471 s
我们可以取出最优模型
automl.model.estimator
结果如下:
XGBRegressor(base_score=0.5, booster='gbtree',
colsample_bylevel=0.5656764254642628, colsample_bynode=1,
colsample_bytree=0.7313266091895249, gamma=0, gpu_id=-1,
grow_policy='lossguide', importance_type='gain',
interaction_constraints='', learning_rate=0.034786853332414935,
max_delta_step=0, max_depth=0, max_leaves=160,
min_child_weight=32.57408640781376, missing=nan,
monotone_constraints='()', n_estimators=776, n_jobs=-1,
num_parallel_tree=1, random_state=0,
reg_alpha=0.005771390107656191, reg_lambda=1.49126672786588,
scale_pos_weight=1, subsample=0.9152991332236934,
tree_method='hist', use_label_encoder=False, validate_parameters=1,
verbosity=0)
同样可以对 XGBoost 模型绘制特征重要度
import matplotlib.pyplot as plt
plt.barh(X_train.columns, automl.model.estimator.feature_importances_)

(4) 模型存储
import pickle
with open('automl.pkl', 'wb') as f:
pickle.dump(automl, f, pickle.HIGHEST_PROTOCOL)
(5) 测试集预估及模型评估
# 测试集预估
y_pred = automl.predict(X_test)
print('Predicted labels', y_pred)
print('True labels', y_test)
# 测试集评估
from flaml.ml import sklearn_metric_loss_score
print('r2', '=', 1 - sklearn_metric_loss_score('r2', y_pred, y_test))
print('mse', '=', sklearn_metric_loss_score('mse', y_pred, y_test))
print('mae', '=', sklearn_metric_loss_score('mae', y_pred, y_test))
3.5 LightGBM 自动调优
LightGBM 调优的过程和 XGBoost 非常类似,仅仅在参数配置的部分指定模型需要做一点调整,其他部分是一致的,如下:
# 导入工具库
from flaml import AutoML
automl = AutoML()
# 参数配置
settings = {
"time_budget": 240, # total running time in seconds
"metric": 'r2', # primary metrics for regression can be chosen from: ['mae','mse','r2','rmse','mape']
"estimator_list": ['lgbm'], # list of ML learners; we tune lightgbm in this example
"task": 'regression', # task type
"log_file_name": 'houses_experiment.log', # flaml log file
"seed": 7654321, # random seed
}
# 自动化机器学习拟合调优
automl.fit(X_train=X_train, y_train=y_train, **settings)
参考资料
ShowMeAI系列教程推荐
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
相关文章推荐
- Python 机器学习算法应用实践
- SKLearn 入门与简单应用案例
- SKLearn 最全应用指南
- XGBoost 建模应用详解
- LightGBM 建模应用详解
- Python 机器学习综合项目-电商销量预估
- Python 机器学习综合项目-电商销量预估<进阶方案>
- 机器学习特征工程最全解读
- 自动化特征工程工具 Featuretools 应用
- AutoML 自动化机器学习建模



浙公网安备 33010602011771号