支持向量机
一、控制正则项的重要程度的C值
假设存在这样的两类点,我们可以学得一条决策边界将它们分开,比如是条这样的直线:
但是得到的决策边界不唯一,根据选择训练集数据的顺序可以得到不同的决策边界:
但是这两条决策边界的泛化能力都不好,为什么这么说呢,因为这两条线都离某个类别的点太近了,很可能测试集中未知的点会被错误的分类。
那么什么样的决策边界才是最好的呢?
这就是一条比较好的决策边界,它离红色样本点和蓝色样本点一样远。在两个类别中,离决策边界最近的那些点都尽可能的远。红色样本有两个点,蓝色样本有一个点,这三个点到决策边界的距离是所有样本点中最近的,且距离是相等的。
这三个点定义出了两条和决策边界平行的线。这两条平行线之间没有任何的样本点。这就是支持向量机的思想。
SVM尝试找到中间那条最优的决策边界,这个决策边界距离两个类别最近的样本最远。
这些最近的样本点就是支持向量,这也是为什么叫支持向量机。
未经标准化的原始数据点分布
import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
iris=datasets.load_iris()
X=iris.data
y=iris.target
#只取y<2的类别,也就是0 1 并且只取前两个特征
X=X[y<2,:2]
# 只取y<2的类别 # 分别画出类别0和1的点
y=y[y<2]
plt.scatter(X[y==0,0],X[y==0,1],color='red')
plt.scatter(X[y==1,0],X[y==1,1],color='blue')
plt.show()
# 标准化
standardScaler=StandardScaler()
#计算训练数据的均值和方差
standardScaler.fit(X)
X_standard=standardScaler.transform(X)#再用scaler中的均值和方差来转换X,使X标准化
#线性SVM分类器
svc=LinearSVC(C=1e9)
# 训练svm
svc.fit(X_standard,y)
LinearSVC(C=1000000000.0, class_weight=None, dual=True, fit_intercept=True,
intercept_scaling=1, loss='squared_hinge', max_iter=1000,
multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
verbose=0)
绘制决策边:
def plot_decision_boundary(model,axis):
x0,x1=np.meshgrid(np.linspace(axis[0],axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),np.linspace(axis[2],axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1))
X_new=np.c_[x0.ravel(),x1.ravel()]
y_predict=model.predict(X_new)
z=y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap=ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0,x1,z,linewidth=5,cmp=custom_cmap)#绘制决策边
plot_decision_boundary(svc,axis=[-3,3,-3,3])
plt.scatter(X_standard[y==0,0],X_standard[y==0,1],color='red')
plt.scatter(X_standard[y==1,0],X_standard[y==1,1],color='blue')
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
改变C值:
svc2=LinearSVC(C=0.01)
svc2.fit(X_standard,y)
plot_decision_boundary(svc2,axis=[-3,3,-3,3])
plt.scatter(X_standard[y==0,0],X_standard[y==0,1],color='red')
plt.scatter(X_standard[y==1,0],X_standard[y==1,1],color='blue')
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
可以很明显的看到和第一个决策边界的不同,在这个决策边界汇总,有一个红点是分类错误的。C 越小容错空间越大。我们可以通过 svc.coef_ 来获取学习到的权重系数, svc.intercept_ 获取偏差。
二、使用多项式特征和核函数
#使用多项式特征和核函数
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
#使用月亮数据集生成的数据
X,y=datasets.make_moons()
print(X.shape)
print(y.shape)
(100, 2)
(100,)
#绘制生成的数据
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
#增加噪声点
X,y=datasets.make_moons(noise=0.15,random_state=777)#随机生成噪声点,random_state是随机种子,noise是方差
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
#使用svm进行分类
from sklearn.preprocessing import PolynomialFeatures,StandardScaler
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
def PolynomialSVC(degree,C=1.0):
#先生成多项式,再标准化,最后生成svm
return Pipeline([("poly",PolynomialFeatures(degree=degree)),("std_scaler",StandardScaler()),("linearSVC",LinearSVC(C=C))])
poly_svc=PolynomialSVC(degree=3)
poly_svc.fit(X,y)
plot_decision_boundary(poly_svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
可以看到生成的边界不是直线了。我们还可以使用核技巧来对数据进行处理,使其维度提升,使原本线性不可分的数据,在高维空间变成线性可分的。再用线性SVM来进行处理。
from sklearn.svm import SVC
def PolynomialKernelSVC(degree,C=1.0):
return Pipeline([("std_scaler",StandardScaler()),("kernelSVC",SVC(kernel="poly"))])#poly代表多项式特征
poly_kernel_svc=PolynomialKernelSVC(degree=3)
poly_kernel_svc.fit(X,y)
plot_decision_boundary(poly_kernel_svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
可以看到这种方式也生成了一个非线性的边界。
这里 SVC(kernel=“poly”) 有个参数是 kernel ,就是核函数。下面介绍下核函数。
三、核函数
SVM的本质是求解这样一个最优化问题:
我们通过对偶形式可以将它转换一下:
上面后面的 x_ix_j x ix j,我们将它们转换成多项式的特征:
最后变成要求解
这个函数是存在的,它就是核函数。这也是使用核函数的技巧,所以有时你会听到核技巧这个概念。
在(二次)多项式核函数中,它的定义是这样的:
K(x,y) = (x \cdot y +1)^2 K ( x , y ) = ( x ⋅ y + 1 ) 2
这两个向量的点乘可以写成求和的形式。
展开后如上。其实这个式子就可以看出若干项相乘再相加。
相当于原来的 x 变成了这样(包含了二次项):
y 也根据这个规则变成了 y^\prime y ′ ,这两新的向量相乘再相加,结果就是
除了多项式核函数,还有很多不同的核函数,其中最有名的就是RBF核函数(高斯核函数)。
RBF核函数:
import numpy as np
import matplotlib.pyplot as plt
x=np.arange(-4,5,1)#生成测试数据
y=np.array((x>=2)&(x<=2),dtype='int')
plt.scatter(x[y==0],[0]*len(x[y==0]))#x取y=0的点,y取0,有多少个x就有多少个y
plt.scatter(x[y==1],[0]*len(x[y==1]))
plt.show()
接下来使用高斯核函数,看如何将一个一维的数据映射到二维的空间:
#接下来使用高斯核函数,看如何将一个一维的数据映射到二维的空间。
#高斯函数
def gaussian(x,l):
gamma=1.0
return np.exp(-gamma*(x-l)**2)
l1,l2=-1,1
X_new=np.empty((len(x),2))
for i,data in enumerate(x):
X_new[i,0]=gaussian(data,l1)
X_new[i,1]=gaussian(data,l2)
plt.scatter(X_new[y==0,0],X_new[y==0,1])
plt.scatter(X_new[y==1,0],X_new[y==1,1])
plt.show()
对于这样的二维数据显然是线性可分的:
接下来用代码来演示下 \gamma γ 的取值对结果的影响。
首先是生成我们的数据:
#生成数据
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
X,y=datasets.make_moons(noise=0.15,random_state=777)
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
#定义一个RBF核的核函数
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
def RBFKernelSVC(gamma=1.0):
return Pipeline([('std_scaler',StandardScaler()),('svc',SVC(kernel='rbf',gamma=gamma))])
svc=RBFKernelSVC()
svc.fit(X,y)
plot_decision_boundary(svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
这是我们设置 \gamma=1.0 γ = 1 . 0 时所得到的决策边界。我们调整下它的值再试下:
#定义一个RBF核的核函数
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
def RBFKernelSVC(gamma=1.0):
return Pipeline([('std_scaler',StandardScaler()),('svc',SVC(kernel='rbf',gamma=gamma))])
svc=RBFKernelSVC(100) #gamma=100
svc.fit(X,y)
plot_decision_boundary(svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
\gamma γ 取值越大,就是高斯分布的钟形图越窄,这里相当于每个样本点都形成了钟形图。很明显这样是过拟合的.
我们再设一下 \gamma γ:
#定义一个RBF核的核函数
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
def RBFKernelSVC(gamma=1.0):
return Pipeline([('std_scaler',StandardScaler()),('svc',SVC(kernel='rbf',gamma=gamma))])
svc=RBFKernelSVC(10) #gamma=10
svc.fit(X,y)
plot_decision_boundary(svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
#定义一个RBF核的核函数
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
def RBFKernelSVC(gamma=1.0):
return Pipeline([('std_scaler',StandardScaler()),('svc',SVC(kernel='rbf',gamma=gamma))])
svc=RBFKernelSVC(0.1) #gamma=0.1
svc.fit(X,y)
plot_decision_boundary(svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()
C:\Users\Administrator\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: UserWarning: The following kwargs were not used by contour: 'linewidth', 'cmp'
此时它是欠拟合的。
因此,我们可以看出 \gamma γ 值相当于在调整模型的复杂度。
SVM解决回归问题:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
boston=datasets.load_boston()
X=boston.data
y=boston.target
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=777)
from sklearn.svm import LinearSVR
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
def StandardLinearSVR(epsilon=0.1):
return Pipeline([('sta_scaler',StandardScaler()),('linearSVR',LinearSVR(epsilon=epsilon))])
svr=StandardLinearSVR()
svr.fit(X_train,y_train)
svr.score(X_test,y_test)
0.6984783816165197