国产化驱动经济自主性与科技创新的未来之路
1499
2022-11-17
【第1&2章】ML基础和监督学习算法(附sklearn花式调参)
学习心得
sklearn工具包很多模型需要调参,但是别慌,参数可分为两种: (1)一种是影响模型在训练集上的准确度或影响防止过拟合能力的参数; (2)另一种不影响这两者的其他参数。 模型在样本总体上的准确度(简称准确度)由其在训练集上的准确度及其防止过拟合的能力所共同决定,所以在调参时,我们主要对第一种参数进行调整,最终达到的效果是:模型在训练集上的准确度和防止过拟合能力的大和谐。
文章目录
学习心得第一章:基础知识
零、学习总结一、基础工具包使用
(1)pandas使用(2)SciPy使用(3)matplotlib
二、小栗子应用
1.初识数据2.划分训练&测试数据3.观察数据4.构建KNN模型预测鸢尾花
第二章:监督学习
零、学习总结一、前置知识
(1)分类与回归(2)泛化、过拟合、欠拟合
二、监督学习算法
2.0 了解几个样本数据集2.1 KNN2.2 线性模型
1.用于回归的线性模型2.线性回归(最小二乘法)3.正则化regularization4.用于分类的线性模型5.用于多分类的线性模型
tips:方法链
6.线性模型小结
2.3 朴素贝叶斯分类器2.4 决策树
(1)控制树的高度(2)分析决策树(3)特征重要性(4)决策树优缺点
2.5 决策树集成
1.随机森林(1)构造随机森林(2)分析随机森林(3)优缺点2.梯度提升回归树(梯度提升机)
2.6 核支持向量机
(1)线性or非线性特征(2)核技巧(3)理解SVM(4)SVM调参(5)SVM预处理数据
2.7 深度学习
(1)花式调参(2)人工数据缩放:(3)神经网络学到了啥(4)总结:
三、分类器的不确定度估计
3.1 决策函数3.2 预测概率3.3 多分类问题的不确定度
四、本章小结
Reference
第一章:基础知识
零、学习总结
(1)scikit-learn 依赖于另外两个 python 包:NumPy 和 SciPy。若想绘图和进行交互式开发,还应该安装 matplotlib、IPython 和 Jupyter Notebook。 (2)如果不熟悉 NumPy 或 matplotlib,推荐阅读 SciPy 讲稿((3)《python机器学习基础》代码:numpy as npimport matplotlib.pyplot as pltimport pandas as pdimport
本系列原书代码的包版本如下,不一定要相同,但是sklearn不能低于0.18(0.18增加了model_selection模块)。
Python version: 3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul 2 2016, 17:53:06)[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)]pandas version: 0.18.1matplotlib version: 1.5.1NumPy version: 1.11.1SciPy version: 0.17.1IPython version: 5.1.0scikit-learn version: 0.18
(4)如果不使用魔法命令,如果你没有使用 Jupyter Notebook 或 魔法命令%matplotlib notebook或%matplotlib inline 来显示图像,那么就需要调用 plt.show 来显示图像. scikit-learn 中的约定:数组中包含 150 朵不同的花的测量数据。前面说过,机器学习中的个体叫作样本(sample),其属性叫作特征(feature)。data 数组的形状(shape)是样本数乘以特征数。
一、基础工具包使用
(1)pandas使用
可以参考之前的总结(【小白学数据分析】task1-数据基础操作和pandas)。
一个pandas DataFrame是一张类似excel的表格(有大量修改和操作表格的方法,尤其可以像SQL一样对表格进行查询和连接)Numpy要求数组中所有元素类型相同,而pandas每一列的数据类型可以互不相同pandas可以从SQL、Excel文件和逗号分隔值(CSV)文件。
如现在如下创建关于人的简单数据集,利用字典创建DataFrame:
import pandas as pd# create a simple dataset of peopledata = {'Name': ["John", "Anna", "Peter", "Linda"], 'Location' : ["New York", "Paris", "Berlin", "London"], 'Age' : [24, 13, 53, 33] } data_pandas = pd.DataFrame(data)# IPython.display allows "pretty printing" of dataframes# in the Jupyter notebookdisplay(data_pandas)
(2)SciPy使用
SciPy 中最重要的是 scipy.sparse(稀疏矩阵):
from scipy import sparse# 创建一个二维对角单位矩阵eye = np.eye(4)print("NumPy array:\n", eye)# NumPy array: [[1. 0. 0. 0.] [0. 1. 0. 0.] [0. 0. 1. 0.] [0. 0. 0. 1.]]
CSR格式的稀疏矩阵:
# Convert the NumPy array to a SciPy sparse matrix in CSR format# 将Numpy数组转换成CSR格式的稀疏矩阵# Only the nonzero entries are stored# 只保存非零元素sparse_matrix = sparse.csr_matrix(eye)print("\nSciPy sparse CSR matrix:\n", sparse_matrix)# SciPy sparse CSR matrix: (0, 0) 1.0 (1, 1) 1.0 (2, 2) 1.0 (3, 3) 1.0
COO格式的稀疏矩阵:
data = np.ones(4)row_indices = np.arange(4)col_indices = np.arange(4)eye_coo = sparse.coo_matrix((data, (row_indices, col_indices)))print("COO representation:\n", eye_coo)# COO representation: (0, 0) 1.0 (1, 1) 1.0 (2, 2) 1.0 (3, 3) 1.0
(3)matplotlib
%matplotlib inlineimport matplotlib.pyplot as pltimport numpy as np# Generate a sequence of numbers from -10 to 10 with 100 steps in betweenx = np.linspace(-10, 10, 100)# Create a second array using siney = np.sin(x)# The plot function makes a line chart of one array against anotherplt.plot(x, y, marker="x")
二、小栗子应用
1.初识数据
2.划分训练&测试数据
3.观察数据
4.构建KNN模型预测鸢尾花
fit、predict 和score 方法是 scikit-learn 监督学习模型中最常用的接口。
(1)k 近邻分类算法,根据新数据点在训练集中距离最近的邻居来进行预测。该算法在KNeighborsClassifier 类中实现,里面既包含构建模型的算法,也包含利用模型进行预测的算法。我们将类实例化,并设定参数。 (2)然后调用 fit 方法来构建模型,传入训练数据(X_train)和训练输出(y_trian)作为参数。 (3)我们用 score 方法来评估模型,该方法计算的是模型精度。 我们将 score 方法用于测试集数据和测试集标签,得出模型的精度约为 97%,也就是说,该模型在测试集上 97% 的预测都是正确的.
from sklearn.neighbors import KNeighborsClassifierfrom sklearn.model_selection import train_test_splitknn = KNeighborsClassifier(n_neighbors=1)X_train, X_test, y_train, y_test = train_test_split( iris_dataset['data'], iris_dataset['target'], random_state=0)knn = KNeighborsClassifier(n_neighbors=1)# 要将该类实例为一个对象,才能用这个模型knn.fit(X_train, y_train)# 基于训练集来构建模型,调用knn对象的fit方法 print("Test set score: {:.2f}".format(knn.score(X_test, y_test)))# 输出Test set score: 0.97
所以知道模型在测试集的精度约为0.97。 我们要对测试数据中的每朵鸢尾花进行预测,并将预测结果与标签(已知的品种)进行对比。 上面我们是通过使用knn对象的score方法来计算测试集的精度;我们也可以通过计算精度(品种预测正确的花所占的比例)衡量模型优劣:
print("Test set score: {:.2f}".format(np.mean(y_pred == y_test)))# 打印Test set score: 0.97
如果要预测一朵鸢尾花(花萼长 5cm 宽2.9cm,花瓣长 1cm 宽0.2cm)属于啥品种,可将该数据放在一个Numpy数组(数组形状为样本数1乘以特征数4),注意sklearn的输入数据必须是二维数组(所以要再转换为二维Numpy数组的一行):
X_new = np.array([[5, 2.9, 1, 0.2]])print("X_new.shape: {}".format(X_new.shape))# 打印X_new.shape: (1, 4)prediction = knn.predict(X_new)print("Prediction: {}".format(prediction))print("Predicted target name: {}".format( iris_dataset['target_names'][prediction]))
结果如下,即通过knn对象的predict方法预测出该花属于类别0(setosa品种),根据上面的精度97%,所以我们有97%的把握预测是对的。
Prediction: [0]Predicted target name: ['setosa']
第二章:监督学习
零、学习总结
(1)sklearn中的数据集通常被保存为Bunch对象(和字典类似,Bunch可用点操作符来访问对象的值(比如用bunch.key来代替bunch['key']))。更多的模型的参数和选项可以参考sklearn官方文档(了解几个样本数据集
(1)从特征较少的数据集(也叫低维数据集)中得出的结论可能并不适用于特征较多的数据集(也叫高维数据集)。
(2)包含在sklearn中的数据集通常被保存为Bunch对象(类似字典),可以用bunch.key代替bunch['key']。如下加载sklearn自带的癌症数据集(benign良性,malignant恶性)。
from sklearn.datasets import load_breast_cancercancer = load_breast_cancer()print("cancer.keys():\n", cancer.keys())# cancer.keys():# dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])print("Shape of cancer data:", cancer.data.shape)# Shape of cancer data: (569, 30)print("Sample counts per class:\n", {n: v for n, v in zip(cancer.target_names, np.bincount(cancer.target))})# Sample counts per class:# {'malignant': 212, 'benign': 357}print("Feature names:\n", cancer.feature_names)"""Feature names: ['mean radius' 'mean texture' 'mean perimeter' 'mean area' 'mean smoothness' 'mean compactness' 'mean concavity' 'mean concave points' 'mean symmetry' 'mean fractal dimension' 'radius error' 'texture error' 'perimeter error' 'area error' 'smoothness error' 'compactness error' 'concavity error' 'concave points error' 'symmetry error' 'fractal dimension error' 'worst radius' 'worst texture' 'worst perimeter' 'worst area' 'worst smoothness' 'worst compactness' 'worst concavity' 'worst concave points' 'worst symmetry' 'worst fractal dimension']"""
(3)需要扩展这个数据集,输入特征不仅包括这 13 个测量结果,还包括这些特征之间的乘积(也叫交互项)。我们不仅将犯罪率和公路可达性作为特征,还将犯罪率和公路可达性的乘积作为特征。像这样包含导出特征的方法叫作特征工程。
2.1 KNN
模型训练。
from sklearn.model_selection import train_test_splitX, y = mglearn.datasets.make_forge()X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.neighbors import KNeighborsClassifierclf = KNeighborsClassifier(n_neighbors=3)clf.fit(X_train, y_train)print("Test set predictions:", clf.predict(X_test))# 打印Test set predictions: [1 0 1 0 1 0 0]print("Test set accuracy: {:.2f}".format(clf.score(X_test, y_test)))# 打印Test set accuracy: 0.86
其中上面的mglearn的make_forge数据集函数具体如下:
def make_forge(): # a carefully hand-designed dataset lol X, y = make_blobs(centers=2, random_state=4, n_samples=30) y[np.array([7, 27])] = 0 mask = np.ones(len(X), dtype=np.bool) mask[np.array([0, 1, 5, 26])] = 0 X, y = X[mask], y[mask] return X,
分析KNN模型训练,在xy平面上画出所有测试点的预测结果,根据平面中所属的类别对平面进行着色,并且查看决策边界。如下图即对1个、3个、9个邻居三种情况的决策边界可视化——邻居个数越多,决策边界越平滑。更平滑的边界对应更简单的模型。
假如考虑极端情况,即邻居个数等于训练集中所有数据点的个数, 那么每个测试点的邻居都完全相同(即所有训练点),所有预测结果也完全相同(即训练 集中出现次数最多的类别)。
仅考虑单一近邻时,训练集上的预测结果十分完美。但随着邻居个数的增多,模型变得更简单,训练集精度也随之下降。
from sklearn.datasets import load_breast_cancerfrom sklearn.model_selection import train_test_splitcancer = load_breast_cancer()X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, stratify=cancer.target, random_state=66)training_accuracy = []test_accuracy = []# try n_neighbors from 1 to 10neighbors_settings = range(1, 11)for n_neighbors in neighbors_settings: # build the model clf = KNeighborsClassifier(n_neighbors=n_neighbors) clf.fit(X_train, y_train) # record training set accuracy training_accuracy.append(clf.score(X_train, y_train)) # record generalization accuracy test_accuracy.append(clf.score(X_test, y_test)) plt.plot(neighbors_settings, training_accuracy, label="training accuracy")plt.plot(neighbors_settings, test_accuracy, label="test accuracy")plt.ylabel("Accuracy")plt.xlabel("n_neighbors")plt.legend()
小结:KNN有2个重要参数:邻居个数、数据点之前距离的度量方法;如果训练集很大(特征数很多或者样本数很大),预测速度可能会很慢,对于稀疏数据集(大多数特征的大多数取值为0的数据集)的算法效果也不好。
2.2 线性模型
线性模型有很多种算法。这些算法的区别在于以下两点: • 系数和截距的特定组合对训练数据拟合好坏的度量方法; • 是否使用正则化,以及使用哪种正则化方法。
1.用于回归的线性模型
对于有多个特征的数据集而言,线性模型可以非常强大。特别地,如果特征数量大于训练数据点的数量,任何目标 y 都可以(在训练集上)用线性函数完美拟合。
2.线性回归(最小二乘法)
对于更高维的数据集(即有大量特征的数据集),线性模型将变得更加强大,过拟合的可能性也会变大。
3.正则化regularization
(1)Lasso回归:加入L惩罚项(|a|+|b|+|c|)即参数(a,b,c)的L1-norm(L1范数) (2)ridge回归(即岭回归):加入惩罚项(a^2 + b^2 +c^2)即参数(a,b,c)的L2-norm(L2范数)。ridge约束更强(所以不容易过拟合)。直观上看,系数尽量小(即每个特征对输出的影响应尽可能小)
小结: (1)复杂度更小的模型意味着在训练集上的性能更差,但泛化性能更好。由于我们只对泛化性能感兴趣,所以应该选择 Ridge 模型而不是 LinearRegression 模型。
(2)简单性和训练集性能二者对于模型的重要程度可以由用户通过设置 alpha 参数(默认alpha=1.0)来指定。增大 alpha 会使得系数更加趋向于 0,从而降低训练集性能,但可能会提高泛化性能。
from sklearn.linear_model import Ridgeridge = Ridge().fit(X_train, y_train)print("Training set score: {:.2f}".format(ridge.score(X_train, y_train)))print("Test set score: {:.2f}".format(ridge.score(X_test, y_test)))# Training set score: 0.89# Test set score: 0.75ridge10 = Ridge(alpha=10).fit(X_train, y_train)print("Training set score: {:.2f}".format(ridge10.score(X_train, y_train)))print("Test set score: {:.2f}".format(ridge10.score(X_test, y_test)))#Training set score: 0.79#Test set score: 0.64ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)print("Training set score: {:.2f}".format(ridge01.score(X_train, y_train)))print("Test set score: {:.2f}".format(ridge01.score(X_test, y_test)))# Training set score: 0.93# Test set score: 0.77
更大的 alpha 表示约束更强的模型,所以我们预计大 alpha 对应的 coef_元素比小 alpha 对应的 coef_ 元素要小。这一点可以在下图中得到证实:
plt.plot(ridge.coef_, 's', label="Ridge alpha=1")plt.plot(ridge10.coef_, '^', label="Ridge alpha=10")plt.plot(ridge01.coef_, 'v', label="Ridge alpha=0.1")plt.plot(lr.coef_, 'o', label="LinearRegression")plt.xlabel("Coefficient index")plt.ylabel("Coefficient magnitude")xlims = plt.xlim()plt.hlines(0, xlims[0], xlims[1])plt.xlim(xlims)plt.ylim(-25, 25)plt.legend()
与 Ridge 类似,Lasso 也有一个正则化参数 alpha,可以控制系数趋向于 0 的强度。在上一个例子中,我们用的是默认值 alpha=1.0。为了降低欠拟合,我们尝试减小 alpha。这么做的同时,我们还需要增加 max_iter 的值(运行迭代的最大次数)。
4.用于分类的线性模型
(1)最常见的两种线性分类算法是 Logistic 回归(PS:logistic regression,不是回归算法,是分类算法)和线性支持向量机(linear support vector machine,线性 SVM),前者在linear_model.LogisticRegression 中实现,后者在 svm.LinearSVC(SVC 代表支持向量分类器)中实现。
(2)对于 LogisticRegression 和 LinearSVC,决定正则化强度的权衡参数叫作 C。C 值越大,对应的正则化越弱。换句话说,如果参数 C 值较大,那么 LogisticRegression 和 LinearSVC 将尽可能将训练集拟合到最好,而如果 C 值较小,那么模型更强调使系数向量(w)接近于 0。 较小的 C 值可以让算法尽量适应“大多数”数据点,而较大的 C 值更强调每个数据点都分类正确的重要性。
from sklearn.linear_model import LogisticRegressionfrom sklearn.svm import LinearSVCX, y = mglearn.datasets.make_forge()fig, axes = plt.subplots(1, 2, figsize=(10, 3))for model, ax in zip([LinearSVC(), LogisticRegression()], axes): clf = model.fit(X, y) mglearn.plots.plot_2d_separator(clf, X, fill=False, eps=0.5, ax=ax, alpha=.7) mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax) ax.set_title(clf.__class__.__name__) ax.set_xlabel("Feature 0") ax.set_ylabel("Feature 1")axes[0].legend()
其中上面的mglearn的make_forge数据集函数具体如下:
def make_forge(): # a carefully hand-designed dataset lol X, y = make_blobs(centers=2, random_state=4, n_samples=30) y[np.array([7, 27])] = 0 mask = np.ones(len(X), dtype=np.bool) mask[np.array([0, 1, 5, 26])] = 0 X, y = X[mask], y[mask] return X,
mglearn.plots.plot_linear_svc_regularization()
在中间的图(下图)中,C 值稍大,模型更关注两个分类错误的样本,使决策边界的斜率变大。右侧图中的模型尽量使所有点的分类都正确,但可能无法掌握类别的整体分布。换句话说,这个模型很可能过拟合。
5.用于多分类的线性模型
许多线性分类模型只适用于二分类问题,不能轻易推广到多分类问题(除了逻辑回归)。 所以将二分类推广到多分类算法的方法一般有: (1)一对其余(one-vs rest):每个类别都学习一个二分类模型,将这个类别与其它类别尽量分开——最后生成与类别个数一样多的二分类模型,而在测试点上运行所有这些二分类模型来进行预测(在对应类别上分数最高的分类器则“胜出”,将这个类别标签返回作为预测结果)。
%matplotlib inline# 第一种方法应用在一个简单的三分类数据集上# 每个类别的数据都是从一个高斯分布中采样得出的from sklearn.datasets import make_blobsimport matplotlib.pyplot as pltimport mglearnX, y = make_blobs(random_state=42)# 用mglearn的函数用于分类可视化mglearn.discrete_scatter(X[:, 0], X[:, 1], y) # X,前者对应特征1,后者对应特征2plt.xlabel("Feature 0")plt.ylabel("Feature 1")plt.legend(["Class 0", "Class 1", "Class 2"])# 在上面的训练集上训练一个 LinearSVC 分类器linear_svm = LinearSVC().fit(X, y)print("Coefficient shape: ", linear_svm.coef_.shape)print("Intercept shape: ", linear_svm.intercept_.shape)# 打印出 Coefficient shape: (3, 2)# 打印出 Intercept shape: (3,)
上面代码的打印结果中,coef_ 的形状是 (3, 2),说明 coef_ 每行包含三个类别之一的系数向量,每列包含某个特征(这个数据集有 2 个特征)对应的系数值。现在 intercept_ 是一维数组,保存每个类别的截距。
将这 3 个二类分类器给出的直线可视化:
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)line = np.linspace(-15, 15)for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_, mglearn.cm3.colors): plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)plt.ylim(-10, 15)plt.xlim(-10, 8)plt.xlabel("Feature 0")plt.ylabel("Feature 1")plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1', 'Line class 2'], loc=(1.01, 0.3))
但图像中间的三角形区域属于哪一个类别呢,3 个二类分类器都将这一区域内的点划为“其余”。——答案是分类方程结果最大的那个类别,即最接近的那条线对应的类别。
(2)多分类逻辑回归:与(1)稍不相同,但也是对每个类别都有一个系数向量和一个截距,也使用了相同的预测方法。
tips:方法链
如下方法的调用的拼接(先调用__init__,然后调用fit)成为方法链。
# 用一行代码初始化模型并拟合logreg = LogisticRegression().fit(X_train, y_train)# 法二:logreg = LogisticRegression() y_pred = logreg.fit(X_train, y_train).predict(X_test)
当然也可以在一行代码内完成模型的初始化、拟合和预测过程,但是这样可读性不强,拟合后的回归模型也没有保存在任何变量中(我们即不能查看也不能用来预测其他数据):
# 法三:y_pred = LogisticRegression().fit(X_train, y_train).predict(X_test)
6.线性模型小结
(1)线性模型的主要参数是正则化参数,在回归模型中叫作 alpha,在 LinearSVC 和 LogisticRegression 中叫作 C。alpha 值较大或 C 值较小,说明模型比较简单。
(2)假定只有几个特征是真正重要的,那么你应该用L1 正则化,否则应默认使用 L2 正则化。如果模型的可解释性很重要的话,使用 L1 也会有帮助。
(3)线性模型可以推广到非常大的数据集,对稀疏数据也很有效(训练速度和预测速度快)。利用我们之间见过的用于回归和分类的公式,理解如何进行预测是相对比较容易的。
(4)特征数大于样本数,线性模型的表现通常很好。
2.3 朴素贝叶斯分类器
BernoulliNB 分类器计算每个类别中每个特征不为 0 的元素个数。
小栗子:有 4 个数据点,每个点有 4 个二分类特征。一共有两个类别:0 和 1。对于类别 0(第 1、3 个数据点),第一个特征有 2 个为零、0 个不为零,第二个特征有 1 个为零、1 个不为零,以此类推。然后对类别 1 中的数据点计算相同的计数。计算每个类别中的非零元素个数:
X = np.array([[0, 1, 0, 1], [1, 0, 1, 1], [0, 0, 0, 1], [1, 0, 1, 0]])y = np.array([0, 1, 0, 1])counts = {}for label in np.unique(y): # 对每个类别label进行遍历 # 计算(求和)每个特征中1的个数 counts[label] = X[y == label].sum(axis=0)print("Feature counts:\n", counts)# Feature counts:# {0: array([0, 1, 0, 2]), 1: array([2, 0, 2, 1])}
小结: (1) alpha 的工作原理:算法向数据中添加 alpha 这么多的虚拟数据点,这些点对所有特征都取正值。这可以将统计数据“平滑化”(smoothing)。alpha 越大,平滑化越强,模型复杂度就越低。
(2)朴素贝叶斯模型的许多优点和缺点都与线性模型相同。它的训练和预测速度都很快,训练过程也很容易理解。该模型对高维稀疏数据的效果很好,对参数的鲁棒性也相对较好。朴素贝叶斯模型是很好的基准模型,常用于非常大的数据集,在这些数据集上即使训练线性模型可能也要花费大量时间。
GaussianNB | MultinomialNB | BernoulliNB | |
适用数据 | 高维数据 | 稀疏计数数据 | 稀疏计数数据(比如文本等) |
计算额统计数据类型 | 保存每个类别中每个特征的平均值和标准差。 | 计算每个类别中每个特征的平均值 | 计算每个类别中每个特征不为 0 的元素个数 |
预测公式 | —— | 和线性模型相同 | 和线性模型相同 |
2.4 决策树
注意:-graphviz后,需要将dot(dot格式是一种用于保存图形的文本文件格式)加入到环境变量中(无论是linux还是win环境),否则会报报错:javascript:void(0),另外加入环境变量后好像要重启才可以。
防止过拟合的两种策略:预剪枝和后剪枝。 (1)预剪枝的限制条件可能包括限制树的最大深度、限制叶结点的最大数目,或者规定一个结点中数据点的最小数目来防止继续划分。 (2)scikit-learn 的决策树在 DecisionTreeRegressor 类和 DecisionTreeClassifier 类中实现。scikit-learn 只实现了预剪枝,没有实现后剪枝。
%matplotlib inlineimport mglearnimport matplotlib.pyplot as pltmglearn.plots.plot_tree_progressive()
没错。。生成的下图右图就是有点模糊。下图左图有点过拟合了——,可以看到该图右方,这个决策边界(蓝色竖线)过于关注远离同类别其他点的单个异常点,即有一小条属于类别0的区域,包围着最右侧属于类别0的那个点。
(1)控制树的高度
限制树的深度可以减少过拟合。这会降低训练集的精度,但可以提高测试集的精度:
from sklearn.tree import DecisionTreeClassifierfrom sklearn.datasets import load_breast_cancerfrom sklearn.model_selection import train_test_splitcancer = load_breast_cancer()X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, stratify=cancer.target, random_state=42)tree = DecisionTreeClassifier(random_state=0)tree.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))# Accuracy on training set: 1.000# Accuracy on test set: 0.937# 限制树的高度,减少过拟合tree = DecisionTreeClassifier(max_depth=4, random_state=0)tree.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))# Accuracy on training set: 0.988# Accuracy on test set: 0.951
(2)分析决策树
可以用tree模块的export_graphviz函数将树可视化,该函数还会生成一个.dot格式的文件(保存图形),在jupyter notebook上能直接显示出图片,也可以直接调用gvedit.exe(在GraphViz的bin目录下)打开.dot文件就能得到基于乳腺癌数据集构造的决策树图。
from sklearn.tree import export_graphvizexport_graphviz(tree, out_file="tree.dot", class_names=["malignant", "benign"],# 类别名 feature_names=cancer.feature_names, # 特征名 impurity=False, filled=True) # 由颜色标识不纯度import graphvizwith open("tree.dot") as f: dot_graph = f.read()display(graphviz.Source(dot_graph))# graphviz模块能读取dot文件并将其可视化
每个结点的 samples 给出了该结点中的样本个数,values 给出的是每个类别的样本个数。
(3)特征重要性
from sklearn.tree import DecisionTreeClassifierfrom sklearn.datasets import load_breast_cancerfrom sklearn.model_selection import train_test_splitcancer = load_breast_cancer()X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, stratify=cancer.target, random_state=42)tree = DecisionTreeClassifier(random_state=0)tree.fit(X_train, y_train)print("Feature importances:")print(tree.feature_importances_)# 将决策树的特征重要性进行可视化import numpy as npdef plot_feature_importances_cancer(model): n_features = cancer.data.shape[1] plt.barh(np.arange(n_features), model.feature_importances_, align='center') plt.yticks(np.arange(n_features), cancer.feature_names) plt.xlabel("Feature importance") plt.ylabel("Feature") plt.ylim(-1, n_features)plot_feature_importances_cancer(tree)
可以看到下面的特征重要性数据,对于每个特征来说都有一个介于0和1的数字(0表示毛用木有,1表示完美地预测目标值),所有值的和一定为1。
Feature importances:[0. 0.00752597 0. 0. 0.00903116 0. 0.00752597 0. 0. 0. 0.00975731 0.04630969 0. 0.00238745 0.00231135 0. 0. 0. 0. 0.00668975 0.69546322 0.05383211 0. 0.01354675 0. 0. 0.01740312 0.11684357 0.01137258 0. ]
由下图可视化知道,worst radius是最重要的特征,我们也可以从一开始的二叉树图看出,第一层的划分确实已经将两个类别划分得很好了。
(1)如果某个特征的 feature_importance_ 很小,并不能说明这个特征没有提供任何信息。这只能说明该特征没有被树选中,可能是因为另一个特征也包含了同样的信息。
(2)与线性模型的系数不同,特征重要性始终为正数,也不能直接说明该特征对应哪个类别。特征重要性告诉我们“worst radius”(最大半径)特征很重要,但并没有告诉我们半径大表示样本是良性还是恶性。
(3)DecisionTreeRegressor(以及其他所有基于树的回归模型)不能外推(extrapolate),也不能在训练数据范围之外进行预测。栗子如下(下图的坐标中纵坐标表示RAM对应年份的价格):
import osimport pandas as pdram_prices = pd.read_csv(os.path.join(mglearn.datasets.DATA_PATH, "ram_price.csv"))plt.semilogy(ram_prices.date, ram_prices.price)plt.xlabel("Year")plt.ylabel("Price in $/Mbyte")
我们利用2000年年前的历史数据来预测2000年后的价格,这对训练出的 DecisionTreeRegressor模型 不会产生什么影响,但对LinearRegression 的影响却很大(第四章会讨论)。训练模型并做出预测之后,我们应用指数映射来做对数变换的逆运算。
from sklearn.tree import DecisionTreeRegressorfrom sklearn.linear_model import LinearRegression# use historical data to forecast prices after the year 2000data_train = ram_prices[ram_prices.date < 2000]data_test = ram_prices[ram_prices.date >= 2000]# predict prices based on dateX_train = data_train.date[:, np.newaxis]# we use a log-transform to get a simpler relationship of data to targety_train = np.log(data_train.price)tree = DecisionTreeRegressor(max_depth=3).fit(X_train, y_train)linear_reg = LinearRegression().fit(X_train, y_train)# predict on all dataX_all = ram_prices.date[:, np.newaxis]pred_tree = tree.predict(X_all)pred_lr = linear_reg.predict(X_all)# undo log-transformprice_tree = np.exp(pred_tree)price_lr = np.exp(pred_lr)plt.semilogy(data_train.date, data_train.price, label="Training data")plt.semilogy(data_test.date, data_test.price, label="Test data")plt.semilogy(ram_prices.date, price_tree, label="Tree prediction")plt.semilogy(ram_prices.date, price_lr, label="Linear prediction")plt.legend()
由下图可以发现,线性模型用一条直线对数据做近似,而树模型——一旦输入超出了模型训练数据的范围,模型就只能持续预测最后一个已知数据点:
(4)决策树优缺点
(1)通过设置max_depth、max_leaf_nodes或min_samples_leaf防止过拟合; (2)决策树算法完全不受数据缩放的影响 (3)缺点:即使做了预剪枝,也经常会出现过拟合,泛化性能较差。
2.5 决策树集成
集成:合并多个机器学习模型来构建更加强大模型的方法。有两种集成模型对大量分类和回归的数据集都是有效的——随机森林和梯度提升决策树(都是以决策树为基础)。
1.随机森林
决策树的一个主要缺点是经常对训练数据过拟合。而【随机森林算法】就是构造很多决策树,每棵树都对目标值做出可以接受的预测。随机森林中树的随机化方法有两种: (1)通过选择用于构造树的数据点; (2)通过选择每次划分测试的特征。
(1)构造随机森林
利用RandomForestRegressor 或RandomForestClassifier 的 n_estimators 参数确定构造的树的个数。
两种方法保证了随机森林中所有树都不相同: 1)构造一棵树,需要对数据进行自助采样(即从n_samples个数据点中有放回地重复随机抽取一个样本,共抽取n_samples次),所以可能会有重复元素。 (2)基于新数据集来创造决策树,在每个结点处,算法随机选择特征的一个子集,并对其中一个特征寻找最佳测试,而不是堆每个结点都寻找最佳测试。max_features参数决定选择的特征个数。 总结:由于使用了自助采样,随机森林中构造每棵决策树的数据集都是略有不同的。由于每个结点的特征选择,每棵树中的每次划分都是基于特征的不同子集。
(2)分析随机森林
# 由5棵树组成的随机森林应用到前面说的two_moons数据集上%matplotlib inlinefrom sklearn.ensemble import RandomForestClassifierfrom sklearn.datasets import make_moonsfrom sklearn.model_selection import train_test_splitimport matplotlib.pyplot as pltimport mglearnfrom sklearn.datasets import load_breast_cancer X, y = make_moons(n_samples=100, noise=0.25, random_state=3)X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)forest = RandomForestClassifier(n_estimators=5, random_state=2)forest.fit(X_train, y_train)# RandomForestClassifier(n_estimators=5, random_state=2)fig, axes = plt.subplots(2, 3, figsize=(20, 10))for i, (ax, tree) in enumerate(zip(axes.ravel(), forest.estimators_)): ax.set_title("Tree {}".format(i)) mglearn.plots.plot_tree_partition(X_train, y_train, tree, ax=ax) mglearn.plots.plot_2d_separator(forest, X_train, fill=True, ax=axes[-1, -1], alpha=.4)axes[-1, -1].set_title("Random Forest")mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
下图中前五图分别为5棵随机化的决策树找到的决策边界,第六图有将它们的预测概率取平均后得到的决策边界(当更多树时,该边界也更为平滑):
cancer = load_breast_cancer()X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, random_state=0)forest = RandomForestClassifier(n_estimators=100, random_state=0)forest.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(forest.score(X_train, y_train)))print("Accuracy on test set: {:.3f}".format(forest.score(X_test, y_test)))# Accuracy on training set: 1.000# Accuracy on test set: 0.972
上面打印的精度来看效果不错,还可以调节max_features参数或像单棵决策树那样进行预剪枝。同样随机森林可以像决策树一样给出所有树的【特征重要性】求和并去平均,效果也比单棵树牛逼。
import numpy as npdef plot_feature_importances_cancer(model): n_features = cancer.data.shape[1] plt.barh(np.arange(n_features), model.feature_importances_, align='center') plt.yticks(np.arange(n_features), cancer.feature_names) plt.xlabel("Feature importance") plt.ylabel("Feature") plt.ylim(-1, n_features)plot_feature_importances_cancer(forest)
与单棵决策树类似,随机森林也给了“worst radius”(最大半径)特征很大的重要性,但从总体来看,它实际上却选择“worst perimeter”(最大周长)作为信息量最大的特征。
(3)优缺点
(1)一般不需要反复调节参数,也不需要对数据进行缩放; (2)有时仍然使用决策树是因为需要决策过程的紧凑表示,即基本不可能对上百棵树做出详细的解释(而且随机森林的树深度一般比决策树高,因为用到了特征子集),所以如果需要需要向非专家总结预测过程,使用单棵决策树效果会更好。 (3)维度非常高的稀疏数据(如文本数据),用线性模型可能更为适合(随机森林即使在大数据集上表现效果也挺好,只不过需要更大内存和多个CPU内核-正常电脑都有吧。。) (4)重要参数如下:
可以用n_jobs参数调节使用的内核个数,当n_jobs=-1时表示使用计算机的所有内核。如果希望实验结果可以重现,需要固定random_state参数值n_estimators 和max_features,可能还包括预剪枝选项(如max_depth)。n_estimators 总是越大越好。对更多的树取平均可以降低过拟合,从而得到鲁棒性更好的集成。max_features 决定每棵树的随机性大小,较小的max_features 可以降低过拟合。一般来说,好的经验就是使用默认值:对于分类,默认值是max_features=sqrt(n_features);对于回归,默认值是max_features=n_features。增大max_features 或max_leaf_nodes 有时也可以提高性能。它还可以大大降低用于训练和预测的时间和空间要求。特别的,当max_features等于n_features时,每次划分都要考虑数据集的所有特征,在特征选择的过程中没有添加随机性(不过自助采样依然存在随机性)。
2.梯度提升回归树(梯度提升机)
别被名字欺骗,可以回归也可以分类。 梯度提升用【连续】的方式构造树,每棵树都试图纠正前一棵树的错误;默认梯度提升树没有随机化,而是用了【强预剪枝】,树深度很小。
重要参数: (1)learning_rate学习率用于控制每棵树纠正前一棵树的错误的强度——较高的学习率则每棵树都可以做出较强的修正。学习率越低,就需要更多的树来构建具有相似复杂度的模型。 (2)增大n_estimators可以向集成中添加更多树,也可以增加模型复杂度(模型有更多机会纠正训练集上的错误)。 (3)下面的乳腺癌数据集上应用 GradientBoostingClassifier 的示例。默认使用 100 棵树,最大深度是 3,学习率为 0.1:栗子体现出:减小树的最大深度显著提升了模型性能,而降低学习率仅稍稍提高了泛化性能。
from sklearn.ensemble import GradientBoostingClassifierX_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, random_state=0)gbrt = GradientBoostingClassifier(random_state=0)gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))# Accuracy on training set: 1.000# Accuracy on test set: 0.965# 改变树的最大深度,预剪枝,降低每棵树的复杂度gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))# Accuracy on training set: 0.991# Accuracy on test set: 0.972# 改变学习速率控制纠正前一棵树的强度gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01)gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)gbrt.fit(X_train, y_train)plot_feature_importances_cancer(gbrt)
上图即将特征重要性可视化,worst permeter是最重要的特征。
小结: (1)如果随机森林效果很好,但预测时间太长,或者机器学习模型精度小数点后第二位的提高也很重要,那么切换成梯度提升通常会有用。 (2)将梯度提升应用在大规模问题上,xgboost包在很多数据集上的速度都比sklearn实现更便捷。 (3)随机森林的 n_estimators 值总是越大越好,但梯度提升不同,增大 n_estimators 会导致模型更加复杂,进而可能导致过拟合。通常的做法是根据时间和内存的预算选择合适的n_estimators,然后对不同的learning_rate 进行遍历。
2.6 核支持向量机
之前讲的是线性支持向量机(用于分类任务),核支持向量机(kernelized support vector machine)无法被输入空间的超平面定义。更多数学原理可参考:Hastie、Tibshirani 和 Friedman 合著的《统计学习基础》一书(12 章。
优点:核支持向量机在低维数据和高维数据(即很少特征和很多特征)上表现很好。特别是数据集中所有特征的测量单位相似(比如都是像素密度)而且范围也差不多时适合用SVM。 缺点:预处理数据和调参(gamma和C参数越大则对应模型更为复杂)需要非常小心,难以向非专家进行解释为啥这么预测。
(1)线性or非线性特征
线性模型在低维空间中可能非常有限,能让线性模型更加灵活的方法:通过添加输入特征的交互项或多项式。
%matplotlib inlinefrom sklearn.datasets import make_blobsimport mglearnimport matplotlib.pyplot as pltX, y = make_blobs(centers=4, random_state=8)y = y % 2mglearn.discrete_scatter(X[:, 0], X[:, 1], y)plt.xlabel("Feature 0")plt.ylabel("Feature 1")from sklearn.svm import LinearSVClinear_svm = LinearSVC().fit(X, y)mglearn.plots.plot_2d_separator(linear_svm, X)mglearn.discrete_scatter(X[:, 0], X[:, 1], y)plt.xlabel("Feature 0")plt.ylabel("Feature 1")
用于分类的线性模型只能用一条直线划分数据集,分类效果一般:
增加新特征,利用三维点 (feature0, feature1, feature1 ** 2)建立三维散点图:
# 添加第二个特征的平方,作为一个新特征X_new = np.hstack([X, X[:, 1:] ** 2])from mpl_toolkits.mplot3d import Axes3D, axes3dfigure = plt.figure()# 3D可视化ax = Axes3D(figure, elev=-152, azim=-26)# plot first all the points with y==0, then all with y == 1# 首先画出所有y == 0的点,然后画出所有y == 1的点mask = y == 0ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', cmap=mglearn.cm2, s=60, edgecolor='k')ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', cmap=mglearn.cm2, s=60, edgecolor='k')ax.set_xlabel("feature0")ax.set_ylabel("feature1")ax.set_zlabel("feature1 ** 2")
将上面的数据新表示中,用线性模型(三维空间中的平面)将这两类别分开:
# 线性SVM对扩展后的三维数据集给出的决策边界linear_svm_3d = LinearSVC().fit(X_new, y)coef, intercept = linear_svm_3d.coef_.ravel(), linear_svm_3d.intercept_# show linear decision boundary# 显示线性决策边界figure = plt.figure()ax = Axes3D(figure, elev=-152, azim=-26)xx = np.linspace(X_new[:, 0].min() - 2, X_new[:, 0].max() + 2, 50)yy = np.linspace(X_new[:, 1].min() - 2, X_new[:, 1].max() + 2, 50)XX, YY = np.meshgrid(xx, yy)ZZ = (coef[0] * XX + coef[1] * YY + intercept) / -coef[2]ax.plot_surface(XX, YY, ZZ, rstride=8, cstride=8, alpha=0.3)ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', cmap=mglearn.cm2, s=60, edgecolor='k')ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', cmap=mglearn.cm2, s=60, edgecolor='k')ax.set_xlabel("feature0")ax.set_ylabel("feature1")ax.set_zlabel("feature1 ** 2")
# 将上图给出的决策边界作为两个原始特征的函数ZZ = YY ** 2dec = linear_svm_3d.decision_function(np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()])plt.contourf(XX, YY, dec.reshape(XX.shape), levels=[dec.min(), 0, dec.max()], cmap=mglearn.cm2, alpha=0.5)mglearn.discrete_scatter(X[:, 0], X[:, 1], y)plt.xlabel("Feature 0")plt.ylabel("Feature 1")
如果将线性SVM模型看做原始特征的函数,那它实际上已经不是线性的了——不是一条直线,而是一个椭圆:
(2)核技巧
添加特征可以让线性模型变得更加强大,但是添加特征(比如100维特征空间所有可能的交互项)的计算开销可能会增大很多。有一种可以在更高维空间中学习分类器的【核技巧】——直接计算扩展特征表示中数据点之间的距离(即内积),而不用实际对扩展进行计算。
将数据映射到更高维空间中的2种常用方法: (1)多项式核:在一定阶数内计算原始特征所有可能的多项式(比如feature ** 2 * feature2 ** 5) (2)径向基函数(radial basis function,RBF)核(即高斯核)——对应无限维的特征空间。一种解释是高斯核考虑所有阶数的所有可能的多项式,但阶数越高,特征的重要性越小(遵循指数映射的泰勒展开)。
(3)理解SVM
显然一般位于类别之间即边界上的那些点对于定义决策边界很重要,这些点也叫【支持向量】。
from sklearn.svm import SVCX, y = mglearn.tools.make_handcrafted_dataset() svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y)mglearn.plots.plot_2d_separator(svm, X, eps=.5)mglearn.discrete_scatter(X[:, 0], X[:, 1], y)# plot support vectors# 画出支持向量sv = svm.support_vectors_# class labels of support vectors are given by the sign of the dual coefficients# 支持向量的类别标签由dual_coef_的正负号给出sv_labels = svm.dual_coef_.ravel() > 0mglearn.discrete_scatter(sv[:, 0], sv[:, 1], sv_labels, s=15, markeredgewidth=3)plt.xlabel("Feature 0")plt.ylabel("Feature 1")
分类决策:基于新样本点与支持向量之间的距离以及在训练过程中学到的支持向量重要性(保存在SVC的dual_coef_属性中)。
from sklearn.svm import SVCX, y = mglearn.tools.make_handcrafted_dataset() # 此处的SVM调参在下面一小节会讲svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y)mglearn.plots.plot_2d_separator(svm, X, eps=.5)mglearn.discrete_scatter(X[:, 0], X[:, 1], y)# plot support vectors# 画出支持向量sv = svm.support_vectors_# class labels of support vectors are given by the sign of the dual coefficients# 支持向量的类别标签由dual_coef_的正负号给出sv_labels = svm.dual_coef_.ravel() > 0mglearn.discrete_scatter(sv[:, 0], sv[:, 1], sv_labels, s=15, markeredgewidth=3)plt.xlabel("Feature 0")plt.ylabel("Feature 1")
(4)SVM调参
fig, axes = plt.subplots(3, 3, figsize=(15, 10))for ax, C in zip(axes, [-1, 0, 3]): for a, gamma in zip(ax, range(-1, 2)): mglearn.plots.plot_svm(log_C=C, log_gamma=gamma, ax=a) axes[0, 0].legend(["class 0", "class 1", "sv class 0", "sv class 1"], ncol=4, loc=(.9, 1.2))
首先是将参数gamma从0.1增加到10,当其值较小时(左上角的图的决策边界平滑,越往右的图的决策边界更关注单个点),说明高斯核的半径较大(即很多点之间看做很靠近)。小的gamma值表示决策边界变化很慢(生成复杂度较低的模型)。
接着是参数C从0.1增加到1000:左上方的图,决策边界几乎都是线性的,误分类了一个点,并且这点对边界没有啥影响。左下方的图(即增大了C值),刚才说的点就对决策边界的影响变大,使得改变决策边界和分类正确。
还有一个问题是SVM对参数的设定和数据的缩放非常敏感,它要求所有特征有相似的变化范围。这里可以用箱线图boxplot工具包绘图,看每个特征的最大值和最小值及其范围:
from sklearn.model_selection import train_test_splitfrom sklearn.datasets import load_breast_cancerfrom sklearn.svm import SVCcancer = load_breast_cancer()X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, random_state=0)svc = SVC()# 默认情况下,C=1,gamma=1/n_featuressvc.fit(X_train, y_train)print("Accuracy on training set: {:.2f}".format(svc.score(X_train, y_train)))print("Accuracy on test set: {:.2f}".format(svc.score(X_test, y_test)))# Accuracy on training set: 0.90# Accuracy on test set: 0.94plt.boxplot(X_train, manage_ticks=False)# 箱形图plt.yscale("symlog")plt.xlabel("Feature index")plt.ylabel("Feature magnitude")
得出的箱线图看出,乳腺癌数据集的特征具有完全不同的数量级,对于其他模型(如线性模型)可能是小问题,但对SVM右极大的影响。
(5)SVM预处理数据
核 SVM常用的缩放方法就是将所有特征缩放到 0 和 1 之间。第三章会用MinMaxScaler 预处理,现在我们先人工处理:
# 计算训练集中每个特征的最小值(共有30个特征)min_on_training = X_train.min(axis=0)# 计算训练集中每个特征的范围(最大值-最小值)range_on_training = (X_train - min_on_training).max(axis=0)# 减去最小值,然后除以范围# 这样每个特征都是min=0和max=1X_train_scaled = (X_train - min_on_training) / range_on_trainingprint("Minimum for each feature\n", X_train_scaled.min(axis=0))print("Maximum for each feature\n", X_train_scaled.max(axis=0))# use THE SAME transformation on the test set,# 利用训练集的最小值和范围对测试集做相同的变换X_test_scaled = (X_test - min_on_training) / range_on_trainingsvc = SVC()svc.fit(X_train_scaled, y_train)print("Accuracy on training set: {:.3f}".format( svc.score(X_train_scaled, y_train)))print("Accuracy on test set: {:.3f}".format(svc.score(X_test_scaled, y_test)))# Accuracy on training set: 0.984# Accuracy on test set: 0.972# 上面的模型可能还处于欠拟合(训练集和测试集的性能很接近,但没接近100%)# 所以我们可以增大C值来拟合更为复杂的模型svc = SVC(C=1000)svc.fit(X_train_scaled, y_train)print("Accuracy on training set: {:.3f}".format( svc.score(X_train_scaled, y_train)))print("Accuracy on test set: {:.3f}".format(svc.score(X_test_scaled, y_test)))# Accuracy on training set: 1.000# Accuracy on test set: 0.958
2.7 深度学习
这块一般不用sklearn(不支持GPU)吧,用pytorch搞!
(1)花式调参
多层感知机MLP即需要学习更多的系数(即权重),如每个输入与每个隐单元(隐单元组成了隐层)之间有一个系数(权重)。在计算完每个隐单元的加权求和后,对结果再应用一个非线性函数——校正非线性(relu)或者正切双曲线(tanh),其图像如下:
import numpy as npline = np.linspace(-3, 3, 100)plt.plot(line, np.tanh(line), label="tanh")plt.plot(line, np.maximum(line, 0), label="relu")plt.legend(loc="best")plt.xlabel("x")plt.ylabel("relu(x), tanh(x)")
下面的MLP栗子为使用2个隐层,每个包含10个单元,使用tanh非线性:
%matplotlib inlinefrom sklearn.neural_network import MLPClassifierfrom sklearn.datasets import make_moonsfrom sklearn.model_selection import train_test_splitimport matplotlib.pyplot as plt X, y = make_moons(n_samples=100, noise=0.25, random_state=3)X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)# 使用2个隐层,每个包含10个单元,使用tanh非线性mlp = MLPClassifier(solver='lbfgs', activation='tanh', random_state=0, hidden_layer_sizes=[10, 10])mlp.fit(X_train, y_train)mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3)mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)plt.xlabel("Feature 0")plt.ylabel("Feature 1")
利用L2惩罚项权重趋于0,从而控制神经网络的复杂度(控制神经网络复杂度的方法有很多种:隐层的个数、每个隐层中的单元个数与正则化alpha),默认值很小(弱正则化),以下为不同alpha值对two_moons数据集的影响:
fig, axes = plt.subplots(2, 4, figsize=(20, 8))for axx, n_hidden_nodes in zip(axes, [10, 100]): for ax, alpha in zip(axx, [0.0001, 0.01, 0.1, 1]): mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[n_hidden_nodes, n_hidden_nodes], alpha=alpha) mlp.fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3, ax=ax) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, ax=ax) ax.set_title("n_hidden=[{}, {}]\nalpha={:.4f}".format( n_hidden_nodes, n_hidden_nodes, alpha))
(2)人工数据缩放:
神经网络也要求所有输入特征的变化范围相似,最理想的情况是均值为 0、方差为1(第三章会学用StandardScaler自动完成)。
from sklearn.model_selection import train_test_splitfrom sklearn.datasets import load_breast_cancerfrom sklearn.svm import SVCcancer = load_breast_cancer()X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, random_state=0)# 计算训练集中每个特征的平均值mean_on_train = X_train.mean(axis=0)# 计算训练集中每个特征的标准差std_on_train = X_train.std(axis=0)# 减去平均值,然后乘以标准差的倒数# 如此运算后,mean=0 and std=1X_train_scaled = (X_train - mean_on_train) / std_on_train# 对测试集做相同的变换(使用训练集的平均值和标准差)X_test_scaled = (X_test - mean_on_train) / std_on_trainmlp = MLPClassifier(random_state=0)mlp.fit(X_train_scaled, y_train)print("Accuracy on training set: {:.3f}".format( mlp.score(X_train_scaled, y_train)))print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))# Accuracy on training set: 0.991# Accuracy on test set: 0.965
但是注意有警告——说已经达到最大迭代次数ConvergenceWarning: Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.,所以需要增加迭代次数,换成下面第一句即可:
mlp = MLPClassifier(max_iter=1000, random_state=0)mlp = MLPClassifier(max_iter=1000, alpha=1, random_state=0)
增加迭代次数仅提高了训练集性能,但没有提高泛化性能。们可以尝试降低模型复杂度来得到更好的泛化性能。这里我们选择增大 alpha 参数(变化范围相当大,从 0.0001 到 1),以此向权重添加更强的正则化(参考上面第二句代码)。
(3)神经网络学到了啥
一种方法是查看模型的权重,参考(5))plt.imshow(mlp.coefs_[0], interpolation='none', cmap='viridis')plt.yticks(range(30), cancer.feature_names)plt.xlabel("Columns in weight matrix")plt.ylabel("Input feature")plt.colorbar()
(4)总结:
(1)主要关注模型的定义:层数、每层的结点个数、正则化和非线性。这些参数内容定义了我们想要学习的模型。 (2)深度学习在开始学习之前其权重是随机设置的,这种随机初始化会影响学到的模型。也就是说,即使使用完全相同的参数,如果随机种子不同的话,我们也可能得到非常不一样的模型。 (3)大型神经网络需要很长训练时间,可以用pytorch GPU。
如何学习模型或用来学习参数的算法,这一点由 solver 参数设定。solver 有两个好用的选项: (1)默认选项是adam,在大多数情况下效果都很好,但对数据的缩放相当敏感(因此,始终将数据缩放为均值为 0、方差为 1 是很重要的)。 (2)另一个选项是lbfgs,其鲁棒性相当好,但在大型模型或大型数据集上的时间会比较长。 (3)还有更高级的 sgd选项,许多深度学习研究人员都会用到。sgd选项还有许多其他参数需要调节,以便获得最佳结果。当你开始使用 MLP 时,我们建议使用 adam 和 lbfgs。
三、分类器的不确定度估计
这部分可以参考——scikit-learn工具包中分类模型predict_proba、predict、decision_function用法详解,用到了再来学也行=。=
3.1 决策函数
3.2 预测概率
3.3 多分类问题的不确定度
分类器能够给出预测的不确定度估计,可以通过decision_function和predict_proba函数。
四、本章小结
有些算法对输入数据的表示方式很敏感,特别是特征的缩放。本章大多数算法都可以同时用于分类和回归,而且所有分类算法都可以同时用于二分类和多分类。可以用于不同的sklearn内置数据集,如回归的boston_housing或者diabetes数据集或者用于多分类的digits数据集,更好地感受它们所需的训练时间、分析模型的难易程度以及它们对数据表示的敏感程度。
Reference
(1)sklearn中文文档:(2)
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~