机器学习之监督学习supervised learning

前提是你 提交于 2019-12-06 16:36:06

分类与回归

监督学习的问题主要有两种,分别是分类classification和回归regression。

分类: 分类问题的目的是预测类别标签class label,这些标签来自预定义的可选列表。
回归: 回归任务的目的是预测一个连续值,也叫作浮点数floating-point number,即预测值不是一个类别而是一个数字值。打个比方,假如要根据一个人的年龄学历等feature来预测这个人的收入,那么预测值为一个金额,可以在给定范围内任意取值。

区分分类与回归: 最好的办法就是看输出是否具有某种连续性,如果在可能的结果之间具有连续性,那么它就是一个回归问题。

泛化 generalize: 如果一个模型能对没有见过的数据做出准确的预测,那么就表明这个模型能从训练集generalize到测试集。

过拟合 overfitting 欠拟合 underfitting: 如果我们总想找到最简单的模型,构建与一个对于现有信息量过于复杂的模型,即在拟合模型的时候过分关注训练集的细节,得到了一个与训练集上表现很好但是不能泛化到新数据上的模型,那么就是overfitting过拟合。反之,如果模型过于简单,无法抓住数据的全部内容以及数据中的变化,甚至在训练集上表现就很差,那么就是underfitting欠拟合。
所以,在二者之间存在一个最佳位置,找到这个位置就是我们最想要的模型。

监督学习算法 supervised learning algorithm:
开始介绍最常用的机器学习算法,以及每个算法的复杂度,适用数据,如何构建模型。其中许多算法啥都有分类和回归两种形式。

首先模型都必须基于数据集进行构建,所以我们先了解一下数据集
模拟数据集:

  • 二分类数据集forge数据集:X, y = mglearn.dataset.make_forge()
  • 回归数据集wave数据集:X, y = mglearn.datasets.make_wave(n_samples=40)

现实世界数据集:

  • 分类数据集:乳腺癌数据集,每个肿瘤都被标记为良性或者恶性。30个特征
  • 回归数据集:波士顿放假数据集,根据犯罪率,地理位置等信息预测房价中位数。13个特征

有时候我们还需要拓展训练集,比如输入feature不仅包括这13个features,还可以包括这些feature之间的乘积也叫作交互项 ,像这样导出特征的方法叫做特征工程feature engineering


K近邻:

k-NN算法算是最简单的机器学习算法,构建模型只需要保存训练数据集,想要对新的数据点做出预测,算法会在训练集中找到最近的数据点。

使用k近邻分类classification:

mglearn.plots.plot_knn_classification(n_neighbors=k)
最简单的版本就是考虑一个最近邻k=1,我们还可以考虑k个最近邻,在这种情况下,我们使用投票法来指定label,我们看哪个类别的最近邻更多,就将这个类别作为预测结果。

下面我们应用这个k近邻算法:

from sklearn.model_selection import train_test_split
X, 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 KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)  # 将这个模型实例化
clf.fit(X_train, y_train)  # 使用训练数据集对这个分类器进行拟合
print("Test set predictions:", clf.predict(X_test))  # 对测试数据集进行预测
print("Test set accuracy: {:.2f}".format(clf.score(X_test, y_test)))  # 评估模型泛化能力

下面我们分析这个KNeighborsClassifier:
对于二维数据集,我们可以在平面上画出所有可能的测试点的预测结果,然后根据每个点的所属类别进行着色,这样可以查看两个类别数据的分界线,也就是决策边界decision boundary

fig, axes = plt.subplots(1, 3, figsize=(10, 3))
for n_neighbors, ax in zip([1, 3, 9], axes):
    # the fit method returns the object self, so we can instantiate
    # and fit in one line
    clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)
    mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title("{} neighbor(s)".format(n_neighbors))
    ax.set_xlabel("feature 0")
    ax.set_ylabel("feature 1")
axes[0].legend(loc=3)

然后我们发现k=1的时候,决策边界紧跟着训练数据,k越大,边界越平滑,更平滑的边界对应着更简单的模型。
接下来,我们来画出模型复杂度与泛化能力之间的关系:

from sklearn.datasets import load_breast_cancer
cancer = 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 10
neighbors_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()

在这里插入图片描述
通过这张图,我们可以看到过拟合与欠拟合的一些特性,比如当k=1的时候,模型对于训练数据集上的预测结果非常的完美,但是测试数据集上的精确度比更多近邻的时候精确度低。这样看来,最佳性能的应该在中间的某处,这样对于训练和测试数据集的精度都比较好。

k近邻回归regression:

在回归问题上,使用多个近邻的预测结果为这些邻居的平均值。使用方法跟分类问题基本一致:

from sklearn.neighbors import KNeighborsRegressor
X, y = mglearn.datasets.make_wave(n_samples=40)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
reg = KNeighborsRegressor(n_neighbors=3)
reg.fit(X_train, y_train)
print("Test set predictions:\n", reg.predict(X_test))
print("Test set R^2: {:.2f}".format(reg.score(X_test, y_test)))

对于回归问题的score方法,返回的是R的平方,这个分数也叫做决定系数,位于0~1之间,等于1的时候为完美预测,等于0的时候代表总是预测训练数据集的平均值。

算法总结:
KNeighbors分类器有两个参数,即邻居的个数以及数据点之间距离的度量方法,往往使用较小的邻居数就能得到比较好的结果,距离的度量方法默认使用欧式距离,这个方法大多数情况效果良好。在训练数据集很大的情况下,预测速度会比较慢,这个算法对于features很多的数据集处理很不好,对于大多数feature取值为0的数据集(稀疏数据集)来说,算法效果非常不好。所以这个算法在实践中往往不会用到。

线性模型linear:

线性模型在实践中被广泛使用,线性模型是利用输入features的线性函数来进行预测的。

用于回归regression的线性模型:

y = w[0]*x[0]+w[1]*x[1]+....+w[p]*x[p]+b
x[0]到x[p]为单个数据点的特征,w和b为学习模型的参数,所以对于单一特征来说预测结果是一条直线,对于两个特征是一个平面,更多特征即更高维度是一个超平面。
如果数据集有多个特征,那么线性模型很强大,尤其是当特征数量大于训练数据集数量时,任何目标y都可以在训练集上用线性函数完美的拟合。下面我们介绍许多不同的线性回归模型,这些模型之间的差异就是它们如何从训练数据集中学习参数w和b,以及如何控制模型的复杂度。

  1. 线性回归,也叫作普通最小二乘法,ordinary least squares OLS:
    线性回归寻找参数b和w,使对训练集的预测值与真实的回归目标值之间的均方误差mean squared error最小,这个误差是预测值与真实值之差的平方和除以sample数量。但是线性回归没有参数,所以无法控制模型的复杂度。
from sklearn.linear_model import LinearRegression
X, y = mglearn.datasets.make_wave(n_samples=60)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
lr = LinearRegression().fit(X_train, y_train)
print("lr.coef_:", lr.coef_)  # 【0.394】这个应该是一个numpy数组,每个元素对应一个输入特征,本例只有一个输入特征
print("lr.intercept_:", lr.intercept_)  # -0.031
print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))  # 0.67
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))  # 0.66  与训练集分数太接近说明可能存在欠拟合

对于一维数据集来说,过拟合风险很小,因为模型非常简单,但是对于高维数据集来说,线性模型变得更加强大,过拟合的风险也会变大。然后我们把这个模型用于波士顿房价训练集,结果发现训练集分数为0.95,但是测试集分数为0.61,这是很明显的过拟合标志,所以我们需要一个可以控制模型复杂度的模型,那么标准的线性回归的常用替代方法之一就是岭回归ridge regression。

  1. 岭回归ridge regression:L2正则化
    它的预测公式与普通最小二乘法的公式相同,但是对系数w的选择不仅要在训练集上表现良好,还需要拟合附加的约束,还希望所有的w都应该接近于0,即斜率很小,即每个feature对输出的影响尽可能小的情况下同时给出很好的预测结果。
    这种约束就是正则化regularization,为避免过拟合,岭回归用到的这种被称为L2正则化。
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=10).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge.score(X_train, y_train)))  # 0.89
print("Test set score: {:.2f}".format(ridge.score(X_test, y_test)))  # 0,75

可以发现ridge在训练集上的分数比linear regression要低,但是在测试数据集上的分数更高。因为ridge是一种约束能力更强的模型,所以更不容易存在过拟合。复杂度更小的模型意味着在训练集上性能更差,但是泛化能力更好,但是我们恰恰只对泛化能力感兴趣,所以选择ridge。

ridge模型对于模型的简单性即系数趋向于0以及训练集性能之间做出权衡,可以通过设置alpha参数来设定。上面例子默认使用alpha=1,增大alpha会使得系数更加趋向于0==》降低训练集性能,但是可能会提高泛化能力。对于非常小的alpha可以让系数收到的限制更小,会得到一个与linear regression类似的模型。

无论是岭回归还是线性回归,所有数据集大小对应的训练分数都要高于测试分数,随着模型的可用数据越来越多,两个模型的性能都在提升,最终线性回归追上了岭回归,所以,只要有足够多的训练数据集,正则化就不那么重要了

  1. lasso回归lasso regression:L1正则化
    与ridge regression相似,使用lasso也是约束系数使之接近于0,但是使用的方法不一样,是L1正则化,使用L1正则化的结果是某些系数刚好等于0,这说明某些features被模型完全的忽略。不过这样模型更容易解释,也可以呈现模型最重要的特征。
from sklearn.linear_model import Lasso
lasso = Lasso().fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso.score(X_train, y_train)))  # 0.29
print("Test set score: {:.2f}".format(lasso.score(X_test, y_test)))  # 0.21
print("Number of features used:", np.sum(lasso.coef_ != 0))  # 4

上述例子我们可以发现,模型在训练集和测试集上面的表现都很差,这表示存在欠拟合,可以看到模型中的特征只用到了4个,所以我们让模型更加拟合,适当减少alpha参数的值。同时,我们还需要增加max_iter的值,即运行迭代的最大次数:
lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
这样结果比较好,最后得出使用到模型特征105个中的33个,模型也更容易理解,但是alpha也不能设置得太小,因为可能会消除正则化的效果,使模型过拟合。

我们可以对不同模型的系数进项作图:

plt.plot(lasso.coef_, 's', label="Lasso alpha=1")
plt.plot(lasso001.coef_, '^', label="Lasso alpha=0.01")
plt.plot(lasso00001.coef_, 'v', label="Lasso alpha=0.0001")

plt.plot(ridge01.coef_, 'o', label="Ridge alpha=0.1")
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")

在这里插入图片描述

比较ridge regression和lasso regression:
在实践中,两个模型一般首选ridge regression。但是如果发现数据集特征很多但是只有其中几个是很重要的,那么选择lasso regression可能会更好。在sklearn中,还提供了ElasticNet类,结合了ridge和lasso的惩罚项,在实际操作中这种结合的效果最好,只是需要调节两个参数:L1正则化和L2正则化。

用于分类classification的线性模型:

y = w[0]*x[0]+w[1]*x[1]+....+w[p]*x[p]+b > 0
如果函数值小于0为类别a,如果函数值大于0为类别b,对于所有用于分类的线性模型,这个规则都是通用的。

对于regression的线性模型,输出y是特征的线性模型,是直线,平面或者超平面。但是对于用于分类的线性模型,决策边界是输入的线性函数,也就是说,线性分类器是利用直线,平面或者超平面来分开两个类别的分类器。

最常见的两种线性分类算法是逻辑回归Logistic regression(LogisticRegression中实现)线性支持向量机linear support vector machine(LinearSVC中实现),其中要注意逻辑回归名字中有回归,但是这是一种分类算法。这两个模型都默认使用了L2正则化。但是对于这两个模型,决定正则化强度的权衡参数叫做C,C值越大,对应的正则化越弱,那么LogisticRegression和LinearSVC将尽可能的将训练集拟合到最好,反之,如果C值较小,那么模型更强调使系数向量更接近于0。除此之外,C值还有一个作用,较小的值可以让算法尽量适应大多数数据点,较大的值更强调每个数据点都分类正确的重要性:
在这里插入图片描述

  1. 逻辑回归logistic regression:默认L2正则化
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
logreg = LogisticRegression(C=1).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg.score(X_train, y_train)))  # 0.953
print("Test set score: {:.3f}".format(logreg.score(X_test, y_test)))  # 0.958

这里C=1其实是默认值,我们可以发现C=1给出了相当不错的精度,但是由于训练集和测试集的性能太接近,我们有理由怀疑这个模型可能是欠拟合的,所以我们尝试增加参数C来将训练集拟合得更好。使用C=100时,我们得到0.972/0.965,可以发现我们的猜想是正确的,因为这个更复杂的模型性能更好,得到了更高的训练集和测试集精度。使用C=0.01时,我们得到一个正则化更强的模型,得到精度0.934/0.930,可以看出我们将已经就比较欠拟合的模型变得更加欠拟合了,所以精度双双降低。

逻辑回归默认使用L2正则化,但是如果我们想要一个可解释性更强的模型,使用L1正则化可能会更好,因为这样模型只使用少数几个特征:
lr_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
我们可以使用penalty参数来改变正则方式,也会影响这个模型是使用全部features还是其中的部分features。

  1. LinearSVC:
    很多线性分类器只使用于二分类,不能轻易推广到多类别问题,除了逻辑回归。
    **多分类原理:**将二分类推广到多分类,最常见的方法是one vs rest,即对每个类别都学习一个二分类模型,将这个类别与所有其他的类别尽量分开,这样就生成了与类别数量一样多的二分类模型。然后我们在测试点上运行所有的二分类模型,对应类别上分数最高的二分类模型即为预测结果。

我们使用one vs rest方法应用到三分类数据集上:

from sklearn.datasets import make_blobs
X, y = make_blobs(random_state=42)
linear_svm = LinearSVC().fit(X, y)
print("Coefficient shape: ", linear_svm.coef_.shape)  #(3,2)
print("Intercept shape: ", linear_svm.intercept_.shape)  # (3,)

coef_每行是三个类别之一的系数向量,每列是每个特征对应的系数向量,然而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))

在这里插入图片描述

在这里插入图片描述

总结优缺点:
线性模型的主要参数是正则化参数,在回归模型中为alpha,在LinearSVC和logistic regression中叫做C。alpha较大或者C较小,说明模型比较简单,通常在对数尺度上对这两个参数进行搜索。

还需要确定使用L1还是L2正则化,如果只有几个特征比较重要或者需要一个可解释的模型,那么可以使用L1正则化,否则默认使用L2正则化。

线性模型的训练速度很快,预测速度也很快,如果特征数量大于样本数量,线性模型的表现都比较好,所以常用于非常大的数据集。

朴素贝叶斯分类器Naive Bayes

Naive Bayes(后面简称NB)是与线性模型非常相似的一种分类器,训练速度往往更快,但是泛化能力会比线性分类器稍差。NB高效的原因在于他是通过单独查看每个特征来学习参数的。

SKlearn中有三种朴素贝叶斯分类器:

  • GaussianNB: 用于任意连续数据,保存每个类别中每个特征的平均值和标准差。
  • BernoulliNB: 假定输入数据为二分类数据,主要用于文本数据分类,计算每个类别中特征不为0的元素的个数
  • MultinomialNB: 假定输入数据为计数数据(比如一个单词在一个句子里面出现的次数),主要用于文本数据分类。计算每个类别中每个特征的平均值。

我们在这里解释一个BermoulliNB模型:这个分类器计算每个类别中特征不为0的元素的个数

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])

可以看出我们有4个数据点,每个点有4个二分类特征。从y中我们可以看出一共有两个类别为0和1,对于类别0,第一个和第三个数据点为类别0。即[0,1,0,1]和[0,0,0,1],第一个特征有两个为0,没有不为0的,第二个特征…以此类推,计算每个类别中非0元素个数:

counts = {}
for label in np.unique(y):
    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])}

优缺点总结:
MultinominalNBBernoulliNB只有一个参数alpha,用于控制模型的复杂度。alpha的工作原理:算法向数据中添加alpha这么多个虚拟数据点,这些点对所有的features都取正值。所以这样可以将统计数据平滑化,alpha越大,平滑性越强,模型复杂度越低。但是alpha值对模型的性能并不重要,通常只是对精度略有提高。这两个模型广泛用于稀疏计数数据,比如文本。
GaussianNB主要用于高维数据。

决策树 decision tree:

决策树是广泛用于分类和回归任务的模型,本质上是从一层层if/else问题中进行学习得出结论的:
mglearn.plots.plot_animal_tree()
在这里插入图片描述
如图所示,树的每一个节点代表一个问题或者一个包含答案的终结点(叶节点),树的连线将问题的答案与即将要问到的下一个问题连接起来。

学习决策树其实就是一连串的if/else问题,决策树的决策过程可以用下面的图片来描述:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这样的递归过程生成一颗二元决策树,其中的每一个节点都包含一个测试,对数据进行反复的划分,直到划分后的每一个区域都只包含单一目标值,如果树中某个叶节点包含的数据点的目标值都相同,那么这个叶节点就是pure的。

如果我们构建的决策树所有的叶节点都是纯叶节点,那么构建的模型就会过于复杂,并且对训练集高度过拟合,因为对训练集的精度是100%/。这也并不是我们需要的决策边界,因为这样的决策边界过于关注远离同类别的其他点的单个异常点。防止决策树过拟合的策略:

  • 预剪枝pre-pruning: 及早停止树的增长,比如限制树的最大深度,或者限制叶节点的最大数量
  • 后剪枝,剪枝purning:先构造树,再删除或折叠信息量很少的点。

构造分类问题决策树:
sklearn在DecisionTreeRegressorDecisionTreeClassifier类中只实现了预剪枝

from sklearn.tree import DecisionTreeClassifier

cancer = 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)))  # 1.0
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))   # 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)))  # 0.988
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))  # 0.951

分析决策树:

from sklearn.tree import export_graphviz
# 这个函数会生成一个.dot文件,用于保存图形的文本文件格式
export_graphviz(tree, out_file="tree.dot", class_names=["malignant", "benign"],
                feature_names=cancer.feature_names, impurity=False, filled=True)

import graphviz
# 使用模块读取这个文件将其可视化,也可以使用其他任何能打开dot文件的程序
with open("tree.dot") as f:
    dot_graph = f.read()
display(graphviz.Source(dot_graph))

分析决策树的特征的重要性:
print(tree.feature_importances_)
可以将其可视化,这种方法值得记住:

def 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)

在这里插入图片描述

构建回归问题决策树:
我们使用计算机内存价格和年份来构建决策树,我们利用2000年之前的数据来预测2000年之后的价格,我们同时对比两个模型线性回归模型和决策树模型

from sklearn.tree import DecisionTreeRegressor
# use historical data to forecast prices after the year 2000
data_train = ram_prices[ram_prices.date < 2000]
data_test = ram_prices[ram_prices.date >= 2000]

# predict prices based on date
X_train = data_train.date[:, np.newaxis]
# we use a log-transform to get a simpler relationship of data to target
y_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 data
X_all = ram_prices.date[:, np.newaxis]

pred_tree = tree.predict(X_all)
pred_lr = linear_reg.predict(X_all)

# undo log-transform
price_tree = np.exp(pred_tree)
price_lr = np.exp(pred_lr)

结果发现,线性模型用一条直线对数据做近似,对测试数据已经做出了比较好的预测,但是忽略了训练数据和测试数据之间一些细微的变化。
树模型完美的预测了训练数据,但是如果输入超出了模型训练数据的范围,那么只能持续预测到最后一个已知的数据点,因为树不能在训练数据的范围之外生成新的相应,所有基于树的模型都有这样的缺点。

决策树的优缺点总结:
决策树有两个比较明显的有点:

  • 得到的模型很容易可视化
  • 算法不受数据缩放的影响,因为每一个特征都被单独处理了,所以不需要对特征进项预处理,比如归一化或者标准化

决策树的缺点也很明显:
即使做了预剪枝,决策树还是很容易过拟合,模型的泛化性能很差,所以在实际操作当中,我们使用集成方法来代替使用单一的决策树。

决策树集成

集成是合并多个机器学习模型来构建更加强大的模型的方法,已证明有两种集成模型对大量分类和回归问题都有良好的表现,这两个方法都是以决策树为基础的,随机森林梯度提升决策树

随机森林 random forest:

由于决策树的主要缺点就是容易对训练数据集过拟合,所以随机森林的出现能有效的解决这个问题,随机森林的本质是许多个决策树的集合,随机森林的核心思想是:每棵树的预测性能都很好,但是都会对部分数据过拟合,那么我们可以构造很多树,然后可以对这些树的结果取平均值来降低过拟合,同时还能保持树的预测能力。

现在问题来了,怎么样才能使构造的决策树都不相同?我们有两种方法同时结合使用来保证随机森林中的树都不尽相同:

  • 自主采样 bootstrap sample:
    从n个数据点中有放回的重复随机抽取一个样本,共抽取n次,这样会创建一个与原来的数据集一样大小,但是里面的数据略有不同的数据集,即新的数据集会缺失某些数据,同时也会重复某些数据
  • 修改算法
    在每个节点处,算法随机抽取特征的一个子集,自己的大小由参数max_feature 来控制,然后对其中的一个特征寻找最佳测试,而不是对每一个节点都寻找最佳测试。

这里max_feature是关键性的参数,如果较大,那么随机森林中的树将会非常的相似,反之,随机森林中树的差异很大,为了更好的拟合,选择更高的深度。

构建随机森林:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons

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)
# 这里表示构建了5个决策树
forest.fit(X_train, y_train)
# 树被保存在了forest的estimator_属性里面

随机森林比每一个单独的决策树都过拟合得要小,但是在实际的操作中,我们一般会使用成百上千的决策树,从而得到更平滑的决策边界。随机森林也可以给出特征的重要性,通常给出的特征重要性比单棵决策树给出的更好,因为随机森林中有更多的特征的重要性不为0。

随机森林的优缺点总结:
用于分类和回归的随机森林是目前应用最为广泛的机器学习模型之一,因为它很强大,基本上不需要调节什么参数就能得到很好的模型,也不需要对数据进行缩放,因为里面的单一决策树都是对单个特征做测试。
随机森林重要的需要调节的参数是n_estimators和max_features,前一个参数总是越大越好,但是是在计算机性能允许的范围之内。

梯度提升回归树 gradient boosted decision tree

虽然名字中带有回归,但是模型可以用于回归也可以用于分类。这个方法与随机森林方法不同,梯度提升采用连续的方式构造决策树,每棵树都尝试修复前一棵的错误,所以这个方法没有随机化,但是用到了强预剪枝,所以决策树的深度都很小。

梯度提升的核心思想:合并许多简单的模型,每棵树只能对部分数据做出好的预测,但是添加的树越来越多,相当于迭代的提升性能。

构建梯度提升回归树:

from sklearn.ensemble import GradientBoostingClassifier

X_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)))  # 1.0
# 我们发现训练集的精度为100%,所以很有可能过拟合,我们选择下面的方法降低过拟合
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))  # 0.958
--------------------------
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
# 定义了树的最大深度,一般不超过5
gbrt.fit(X_train, y_train)  # 0.991  0.958
---------------------------
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01)
# learning_rate参数控制每棵树纠正前一棵树错误的强度
gbrt.fit(X_train, y_train)  # 0.988  0.965

如何选择随机森林和梯度提升:
先尝试随机森林,如果随机森林效果很好但是预测时间太长,那么可以尝试使用梯度提升。但是如果要真正使用梯度提升,可以考虑使用xgboost包。

梯度提升的优缺点总结:
该方法是监督学习中最强大也最常用的模型之一,主要缺点是需要仔细的调整参数。主要参数是n_estimator, learning_rate, max_depth,其中决策树的数量不像随机森林那样越大越好,增大数量会导致模型更加复杂,然后导致过拟合。

核支持向量机 kernelized support vector machine (SVM)

前面研究了LinearSVC线性支持向量机用于分类任务,SVM用于推广到更复杂的模型,这些模型通常是无法被输入空间的超平面定义。

神经网络,深度学习:

https://blog.csdn.net/qq_43355223/article/details/86593078

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!