Python机器学习基础教程3

核支持向量机

核支持向量机SVM可以推广到更复杂的模型,这些模型无法被输入空间的超平面定义,支持向量机可以同时用于分类和回归。

线性模型与非线性特征

线性模型在低维空间中可能非常受限,因为线和平面的灵活性有限。使得模型更加灵活就得添加更多的特征:添加输入特征的交互项或多项式项。
线性模型不是线性可分的,用于分类的线性模型只能用一条直线来划分数据点。

x,y = make_blobs(centers=4,random_state=8)
y = y%2
mglearn.discrete_scatter(x[:,0],x[:,1],y)
plt.xlabel("feature 0")
plt.ylabel("feature 1")
plt.show()
# 线性SVM给出的分界线
from sklearn.svm import LinearSVC
linear_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")
plt.show()

在这里插入图片描述
在这里插入图片描述
扩展特征,重新绘制散点图

# 对输入特征进行扩展:添加第二个特征的平方作为一个新的特征
# x[:,1:]取下标从1到全部的数据列
# hstack()元素的堆叠函数
x_new = np.hstack([x,x[:,1:]**2])
figure = plt.figure()
#3D可视化  elev指定查看曲面的高度,azim指定旋转的角度
ax = Axes3D(figure,elev=-152,azim=-26,auto_add_to_figure=True)
# 首先画出所有y==0的点,然后画出所有y==1的点
mask = y==0
ax.scatter(x_new[mask,0],x_new[mask,1],x_new[mask,2],c='b',cmap=mglearn.cm2,s=60)
# 绘制y=1的点
ax.scatter(x_new[~mask,0],x_new[~mask,1],x_new[~mask,2],c='r',marker='^',cmap=mglearn.cm2,s=60)
ax.set_xlabel("feature 0")
ax.set_ylabel("feature 1")
ax.set_zlabel("feature1**2")
plt.show()

在这里插入图片描述
使用线性模型将这两个类别分开

# 显示线性决策边界
figure = plt.figure()
ax = Axes3D(figure,elev=-512,azim=-26)
# np.linspace创建等差数列,开始结点,结束结点,默认的样本量
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)
# np.meshgrid生成网格点坐标矩阵
XX,YY = np.meshgrid(xx,yy)
ZZ =(coef[0]*XX+coef[1]*YY+intercept)/-coef[2]
# 绘制3D图形 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)
ax.scatter(x_new[~mask,0],x_new[~mask,1],x_new[~mask,2],c='r',marker='^',cmap=mglearn.cm2,s=60)
ax.set_xlabel("feature 0")
ax.set_ylabel("feature 1")
ax.set_zlabel("feature1**2")
plt.show()

在这里插入图片描述
如果将线性SVM模型看作原始特征的函数,它不再是一条直线而是一个椭圆

ZZ = YY**2
dec = 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")
plt.show()

在这里插入图片描述

核技巧

向数据表示中添加非线性的特征,可以让线性模型变得强大,但是添加许多特征的计算开销可能会很大,使用核技巧,直接计算扩展特征表示中数据点之间的距离(内积),而不用实际对扩展进行计算。
对于支持向量机,将数据映射到更高维空间中有两种常用的方法:一种是多项式核,再一定阶数内计算原始特征所有可能的多项式;另一种是径向基函数核也称为高斯核,高斯核对应无限维度的特征空间。一种对高斯核的解释是它考虑所有阶数的所有可能的多项式,但阶数越高,特征的重要性就越小。

理解SVM

在训练过程中,SVM学习每个训练数据点对于表示两个类别之间的决策边界的重要性。通常只有一部分训练数据点对于定义决策边界来说非常重要:位于类别之间边界上的那些点,这些点叫做支持向量,支持向量机正式由此得名。
想要对新样本进行预测,需要测量它与每个支持向量之间的距离。分类决策是基于它与支持向量之间的距离以及在训练过程中学习到的支持向量的重要性(保存在SVC的dual_coef_属性中)来做出的。

from sklearn.svm import SVC
x,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=0.5)
mglearn.discrete_scatter(x[:,0],x[:,1],y)
# 画出支持向量
sv = svm.support_vectors_
# 支持向量的类别标签由dual_coef_的正负号给出
sv_labels = svm.dual_coef_.ravel()>0
mglearn.discrete_scatter(sv[:,0],sv[:,1],sv_labels,s=15,markeredgewidth=3)
plt.xlabel("feature0")
plt.ylabel("feature1")
plt.show()

在这里插入图片描述

SVM调参

SVM给出了非常平滑且非线性的边界,调节了两个参数gamma和C。gamma参数是公式中的参数,用于控制高斯核的宽度,它决定了点与点之间"靠近"是指多大的距离。C是正则化参数,与线性模型中用到的类似,限制每个点的重要性(每个点的dual_coef_)

# 改变参数
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))
plt.show()

在这里插入图片描述
从左到右,我们将参数 gamma 的值从 0.1 增加到 10。gamma 较小,说明高斯核的半径较大,许多点都被看作比较靠近。这一点可以在图中看出:左侧的图决策边界非常平滑,越向右的图决策边界更关注单个点。小的 gamma 值表示决策边界变化很慢,生成的是复杂度较低的模型,而大的 gamma 值则会生成更为复杂的模型。
从上到下,我们将参数 C 的值从 0.1 增加到 1000。与线性模型相同,C 值很小,说明模型非常受限,每个数据点的影响范围都有限。你可以看到,左上角的图中,决策边界看起来几乎是线性的,误分类的点对边界几乎没有任何影响。再看左下角的图,增大 C 之后这些点对模型的影响变大,使得决策边界发生弯曲来将这些点正确分类。

# 将SVM应用到乳腺癌数据集上,取默认参数
x_train,x_test,y_train,y_test= train_test_split(cancer.data,cancer.target,random_state=0)
svc = SVC()
svc.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)))
plt.plot(x_train.min(axis=0), 'o', label="min")
plt.plot(x_train.max(axis=0), '^', label="max")
plt.legend(loc=4)
plt.xlabel("Feature index")
plt.ylabel("Feature magnitude")
plt.yscale("log")
plt.show()
运行结果:
Accuracy on training set: 1.00
Accuracy on test set: 0.63

存在相当严重的过拟合。虽然 SVM 的表现通常都很好,但它对参数的设定和数据的缩放非常敏感。特别地,它要求所有特征有相似的变化范围,但是乳腺癌的数据集的特征具有完全不同的数量级。
在这里插入图片描述

SVM预处理数据

对每个特征进行缩放,使其大致位于同一范围,核SVM常用的缩放的方法是将所有的特征缩放到0和1之间。

# 计算训练集中每个特征的最小值
min_on_training = x_train.min(axis = 0)
# 计算训练集中每个特征的范围(最大值-最小值)
range_on_training =(x_train-min_on_training).max(axis =0)
# 减去最小值,然后除以范围
# 这样每个特征就是min=0,max=1
x_train_scaled= (x_train-min_on_training)/range_on_training
print("minmum for each feature{}".format(x_train_scaled.min(axis=0)))
print("maxmum for each feature{}".format(x_train_scaled.max(axis=0)))

运行结果:
minmum for each feature[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.]
maxmum for each feature[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.]
# 利用训练集的最小值和范围来对测试机做同样的变换
x_test_scaled = (x_test - min_on_training)/range_on_training
svc = 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

模型处于欠拟合的状态

# 尝试增大C或gamma来集合更为复杂的模型
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
优点缺点

核支持向量机是非常强大的模型,在各种数据集上的表线很好,SVM允许决策边界很复杂,即使数据只有几个特征。他在低维数据核高维数据(即很少的特征和很多的特征)上的表现都很好,但对样本个数的缩放表现不好。SVM的另一个缺点是预处理数据和调参都需要非常小心,同时模型也很难进行解释。当所有特征的测量单位相似而且范围也差不多的时候,SVM是值得尝试的。
核SVM的重要参数是正则化参数C,核的选择以及与核相关的参数。RBF核只有一个参数gamma,它是高斯核宽度的倒数。gamma和C控制的都是模型复杂度,较大的值都对应更为复杂的模型。

神经网络

用于分类和回归的多层感知机MLP,它可以作为复杂深度学习方法的起点。
####神经网络模型
MLP可以被视为广义的线性模型,执行多层处理后得到结论。
在这里插入图片描述
在这里插入图片描述
左边的每个结点代表一个输入特征,连线代表学到的系数,右边的结点代表输出,是输入的加权和。
在MLP中,多次重复这个计算加权求和的过程,首先计算代表中间过程的隐藏单元,然后再计算这些隐藏单元的加权求和并得到最终结果。
在这里插入图片描述
上图这个模型需要学习更多的系数(权重):在每个输入与隐藏单元之间有一个系数,每个隐藏单元与输出之间也有一个系数。
计算一系列加权求和与只计算一个加权求和是完全相同的,为了让模型比线性模型更强大,在计算完每个隐藏单元的加权求和之后再对结果应用一个非线性函数(一般为校正非线性或正切函数),然后将这个函数的结果用于加权求和,计算得到的输出y。relu截断小于0的值,tanh在输入值较小的时候接近-1,在输入值较大时接近+1。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

神经网络调参
from sklearn.neural_network import MLPClassifier
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)
mlp = MLPClassifier(solver='lbfgs', random_state=0).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")

在这里插入图片描述
神经网络学到的决策边界完全是非线性的,但是相对平滑,MLP默认使用100个隐藏点,减少隐藏点数量降低模型的复杂度,也可以得到更好的结果。

mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[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")
plt.show()

在这里插入图片描述
降低隐藏点的数量,决策边界看起来更加参差不齐。如果想要得到更加平滑的决策边界,可以添加更多的隐单元,添加第二个隐层或者使用tanh非线性。

# 使用2个隐层,每个包含10个单元
mlp = MLPClassifier(solver='lbfgs', 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")
plt.show()

# 使用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")
plt.show()

在这里插入图片描述
可以利用L2惩罚使权重趋向于0,控制神经网络的复杂度,和岭回归和线性分类器中做的一样。使用alpha调节L2惩罚的参数,它的默认值很小(弱正则化),展示使用不同的alpha对数据集的影响。

# 绘制子图
fig,axes = plt.subplots(2,4,figsize=(20,8))
# 隐藏单元10100
for axx,n_hidden_nodes in zip(axes,[10,100]):
    # alpha参数 使用2层的隐层
    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))
plt.show()

在这里插入图片描述
控制神经网络复杂程度的方法有很多种:隐层的个数,每个隐层中的单元个数与正则化等。
神经网络一个重要的性质就是:在开始学习之前的权重是随机设置的,即使使用完全相同的参数,如果随机种子不同的话,我们也可能得到非常不一样的模型,如果网络很大,并且复杂度选择合理的话,可能不会对精度有太大的影响。

# 所有模型使用相同的参数设置进行学习
fig, axes = plt.subplots(2, 4, figsize=(20, 8))
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
for i, ax in enumerate(axes.ravel()):
    # axes.ravel()数组维度拉成一维数组 random_state=i随机种子
    mlp = MLPClassifier(solver='lbfgs', random_state=i,hidden_layer_sizes=[100, 100])
    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)
plt.show()

在这里插入图片描述
相同参数但不同随机初始化的情况下学到的决策函数是不同的。

进一步李娇儿神经网络,将MLPClassifier应用在乳腺癌数据集上。

#将MALPClassifirt应用在乳腺癌数据集上,使用默认参数
cancer = load_breast_cancer()
print("cancer data per-feature maxima:\n{}".format(cancer.data.max(axis=0)))
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)
mlp = MLPClassifier(random_state=42)
mlp.fit(X_train, y_train)
print("Accuracy on training set: {:.2f}".format(mlp.score(X_train, y_train)))
print("Accuracy on test set: {:.2f}".format(mlp.score(X_test, y_test)))

运行结果:
Accuracy on training set: 0.94
Accuracy on test set: 0.92

MLP的精度相当好,但是没有其他模型好,可能是在于数据的缩放,神经网络也要求所有输入特征的变化范围相似,最理想的情况是均值为0,方差为1。

# 计算训练集中每个特征的均值
mean_on_train =X_train.mean(axis=0)
# 计算训练集中每个特征的标准差
std_on_train = X_train.std(axis =0)

#减去平均值,然后乘以标准差的倒数
# 如此运算之后,mean=0,std=1
X_train_scaled =(X_train - mean_on_train)/std_on_train
# 对测试集做相同变换
X_test_scaled =(X_test -mean_on_train)/std_on_train
mlp = 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

缩放之后结果要好很多,但是模型给出警告,已经达到最大迭代次数,建议我们应该增大迭代次数

# 增大迭代次数
mlp = MLPClassifier(max_iter=1000, 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: 1.000
Accuracy on test set: 0.972

提高了训练集的性能,但没有提高泛化性能,尝试降低模型的复杂度来得到更好的泛化性能。增大alpha参数,向权重添加更强的正则化。

#提高泛化性能
mlp = MLPClassifier(max_iter=1000, alpha=1, 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.988
Accuracy on test set: 0.972

这是目前最好的模型的性能。但是想要分析神经网络学到什么,比分析线性模型或基于树的模型更为复杂,一种方法是查看模型的权重。

# 查看模型的权重
plt.figure(figsize=(20, 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()
plt.show()

在这里插入图片描述
如果某个特征对所有隐单元的权重都很小,那么这个特征对模型来说就不太重要。或者我们没有用神经网络可以使用的方式来表示这些特征。也可以将连接隐藏层和输出层的权重可视化,但是它们更加难以解释。

优点和缺点

主要优点之一是能够获取大量数据包中包含的信息,并构建无比复杂的模型,给定足够的计算时间和数据,并且仔细调节参数,神经网络通常可以打败其他机器学习算法。
缺点是:通常需要很长的训练时间,还需要仔细的对数据进行预处理,与SVM类似,神经网络在均匀数据上的性能最好,均匀指的是所有特征都具有相似的含义。如果数据包含不同种类的特征,基于树的模型可能会表现的更好。
最重要的参数是层数和每层的隐单元的个数,首先设置1个或2个隐藏,然后可以逐步增加。每个隐层的结点个数通常与输入特征的个数接近,但在几千个结点是很少会多于特征个数。
在这里插入图片描述
神经网络调参的常用方法是,首先创建一个大到足以过拟合的网络,确保这个网络可以对任务进行学习,训练数据可以被学习之后,要么缩小网络,要么增大alpha来增强正则化,提高模型的泛化性能。
在实验中要关注模型的定义:层数,每层结点的个数,正则化和非线性。这些内容定义了我们想要学习的模型。如何学习模型或用来学习参数的算法,这一点是由solver参数设定,solver默认的选项是adam,在大多数情况下效果都很好,但对数据的缩放相当敏感(将数据缩放为均值为0,方差为1)。另一个选项是‘lbfgs’,在大型模型或大型数据集上的时间会比较长。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值