【机器学习实战】-- Titanic 数据集(4)-- 支持向量机

1. 写在前面: 

本篇属于实战部分,更注重于算法在实际项目中的应用。如需对感知机算法本身有进一步的了解,可参考以下链接,在本人学习的过程中,起到了很大的帮助:

统计学习方法 李航

支持向量机原理 https://www.cnblogs.com/pinard/p/6097604.html 等共5篇

空间中任意一点到超平面距离的公式推导 https://www.cnblogs.com/yanganling/p/8007050.html

拉格朗日对偶 https://www.cnblogs.com/ooon/p/5723725.html

 

2. 数据集:

数据集地址:https://www.kaggle.com/c/titanic

Titanic数据集是Kaggle上参与人数最多的项目之一。数据本身简单小巧,适合初学者上手,深入了解比较各个机器学习算法。

数据集包含11个变量:PassengerID、Pclass、Name、Sex、Age、SibSp、Parch、Ticket、Fare、Cabin、Embarked,通过这些数据来预测乘客在Titanic事故中是否幸存下来。

 

3. 算法简介:

支持向量机原本是一种分类模型,和感知机一样,定义了特征空间中的一个超平面,但间隔最大化使其区别于感知机(通常是优于)。此外,和感知机一样,利用Gram矩阵,方便地使用核函数,使其可以成为一个非线性分类器。

支持向量机的学习策略就是间隔最大化,并且等价与合页损失函数(hinge loss)的最小化问题。

 

3.1 线性可分支持向量机模型:

给定一个数据集:$T=\left \{ \left ( x_{1}, y_{1} \right ), \left ( x_{2}, y_{2} \right ), ..., \left ( x_{N}, y_{N} \right ) \right \}$,其中$x_{i}\in \chi\subseteq \bf{R^{n}}$,$y_{i} \in \Upsilon= \left \{+1, -1 \right \}$,$i = 1,2,...,N$。这代表数据集共有 N 对 实例,每个实例 $x_{i}$都是n维的。

假设通过间隔最大化求解得到的分离超平面为:

$$ w^{\ast } \cdot x + b^{\ast} = 0 $$

则如下分类函数即线性可分支持向量机模型:

$$ f(x) = \text{sign}(w^{\ast} \cdot x + b^{\ast}) $$

 

 

3.2 线性可分支持向量机损失函数:

首先,在定义损失函数之前,需要了解函数间隔以及几何间隔:

超平面$(w,b)$关于样本点$(x_{i}, y_{i})$的函数间隔(使用于感知机损失函数):

$$ \hat{\gamma_{i}} = y_{i} (w \cdot x_{i} + b) $$

超平面$(w,b)$关于训练数据集$T$的函数间隔:

$$ \hat{\gamma} = \min_{i=1,...,N} \hat{\gamma_{i}} $$

 

超平面$(w,b)$关于样本点$(x_{i}, y_{i})$的几何间隔:

$$ \gamma_{i} = \frac{y_{i} (w \cdot x_{i})}{\left \| w \right \|} $$

超平面$(w,b)$关于训练数据集$T$的几何间隔:

$$ \gamma = \min_{i=1,...,N}\gamma_{i} $$

 

回想一下,支持向量机的学习策略就是间隔最大化,即最大化超平面$(w,b)$关于训练数据集的几何间隔$\gamma$,可以将其表示为如下的最优化问题:

$$ \max_{w,b} \gamma \quad s.t. \frac{y_{i} (w \cdot x_{i})}{\left \| w \right \|} \geq \gamma , \quad i=1,2,...,N $$

通过函数间隔与几何间隔的关系,将其改写成如下形式:

$$ \max_{w,b} \frac{\hat{\gamma}}{\left \| w \right \|} \quad s.t. y_{i}(w \cdot x_{i} + b)  \geq \hat{\gamma}, \quad i=1,2,...,N$$

显而易见,函数间隔$\hat{\gamma}$的取值不会影响上述优化问题的解,我们可以令其为1。同时将最大化$\frac{1}{||w||}$问题转化为最小化$||w||^{2}$问题,同时为了便于后续的计算,加上$\frac{1}{2}$的系数,于是优化问题转变为:

$$ \min_{w,b} \frac{1}{2}||w||^{2} \quad s.t. y_{i}(w \cdot x_{i} + b) \geq 1, \quad i=1,2,...,N$$

为了求解上述不等式优化问题,可以应用拉格朗日乘子法,得到拉格朗日函数$L$,L即线性可分支持向量机的损失函数:

$$ L(w, b, \alpha) = \frac{1}{2}\left \| w \right \|^2 + \sum_{i=1}^{N}\alpha_{i}\left [ 1 - y_{i}(w \cdot x + b) \right ] $$

通过拉格朗日函数可以得到:

$$ \min_{w,b}\frac{1}{2}\left \| w \right \|^2 = \min_{w,b} \max_{\alpha_{i} \geq 0} L(w,b,\alpha) $$

然后,将其转化成对偶问题:

$$ \min_{w,b}\frac{1}{2}\left \| w \right \|^2 = \max_{\alpha_{i} \geq 0}\min_{w,b} L(w,b,\alpha)  $$

 

3.3 线性可分支持向量机的学习过程:

线性可分支持向量机的学习过程即求解对偶问题的过程:首先求$L(w,b,\alpha)$对$w,b$的极小,再求其对$\alpha$的极大。

第一步:对求$L(w,b,\alpha)$对$w,b$的极小:

$$ \nabla_{w}L(w,b,\alpha) =  w - \sum_{i=1}^{N} \alpha_{i}y_{i}x_{i} = 0 $$

$$ \nabla_{b}L(w,b,\alpha) = \sum_{i=1}^{N} \alpha_{i}y_{i} = 0 $$

可以得到如下等式:

$$ w = \sum_{i=1}^{N} \alpha_{i}y_{i}x_{i} $$

$$ \sum_{i=1}^{N} \alpha_{i}y_{i} = 0 $$

其中,第一个式子将带回原式$L(w,b,\alpha)$,第二个式子将作为第二步求解对$\alpha$的极大时的约束条件。

将第一个式子带回原式$L(w,b,\alpha)$可得:

$$

\begin{align*}
L(w,b,\alpha) &= \frac{1}{2}\left \| w \right \|^2 + \sum_{i=1}^{N}\alpha_{i}(1 - y_{i}w \cdot x_{i}) - y_{i}b \\
&= \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}\left ( x_{i} \cdot x_{j} \right ) + \sum_{i=1}^{N} \alpha_{i} - \sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}\left ( x_{i} \cdot x_{j} \right ) + 0 \\
&= - \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}\left ( x_{i} \cdot x_{j} \right ) + \sum_{i=1}^{N} \alpha_{i}
\end{align*}

$$

第二步:求$\min_{w,b} L(w,b,\alpha)$对$\alpha$的极大,配合约束条件$\sum_{i=1}^{N} \alpha_{i}y_{i} = 0$ 可得:

$$ \max_{\alpha} - \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}\left ( x_{i} \cdot x_{j} \right ) + \sum_{i=1}^{N} \alpha_{i} \quad s.t. \quad \sum_{i=1}^{N}\alpha_{i}y_{i}=0, \quad \alpha_{i} \geq0, \quad i=1,2,...,N  $$

$$ \Downarrow $$

$$ \min_{\alpha}  \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}\left ( x_{i} \cdot x_{j} \right ) - \sum_{i=1}^{N} \alpha_{i} \quad s.t. \quad \sum_{i=1}^{N}\alpha_{i}y_{i}=0, \quad \alpha_{i} \geq0, \quad i=1,2,...,N  $$

以上问题可以通过SMO算法(这里不详细解释)求得$\alpha$的解$\alpha^{\ast}$

最终求得$w^{\ast}$ 和 $ b^{\ast} $ 为:

$$ w^{\ast} = \sum_{i=1}^{N} \alpha_{i}^{\ast}y_{i}x_{i} $$

$$ b_{s}^{\ast} = y_{s} - \sum_{i=1}^{N} \alpha_{i}^{\ast}y_{i}x_{i} \cdot x_{s}, \quad \text{ for $\alpha_{s} > 0$, $(x_{s}, y_{s})$是支持向量 } $$

 

3.4 线性支持向量机:

3.3中介绍的线性可分支持向量机仅适用于线性可分训练数据集,当训练数据集线性不可分时,约束条件 $y_{i}(w \cdot x_{i} + b) \geq \hat{\gamma}, \quad i=1,2,...,N$ 并不对所有数据点都成立。。我们将原来的约束条件称之为硬间隔最大化,为了将支持向量机扩展到线性不可分数据集,我们对其进行一定的修改,修改后的约束条件称之为软间隔最大化。

既然有些样本点$(x_{i}, y_{i})$不能满足函数间隔大于等于1的约束条件,我们对每个样本点$(x_{i}, y_{i})$引入一个松弛变量$\xi_{i} \geq 0$,使函数间隔加上松弛变量大于等于1。同时,由于加入了松弛变量,需要付出代价,在目标函数中加对每个松弛变量加入代价。

$$ \begin{align*}
& \min_{w,b,\xi} \frac{1}{2}\left \| w \right \|^{2} + C\sum_{i=1}^{N}\xi_{i} \\
\text{s.t.} \quad & y_{i}(w \cdot x_{i} + b) \geq 1 - \xi_{i}, \\
& \xi_{i} \geq 0, \quad i=1,2,...,N
\end{align*} $$

依照3.3中相似的步骤,可以得到拉格朗日函数$L$:

$$ L(w,b,\xi,\alpha,\mu) = \frac{1}{2}\left \| w \right \|^2 + C\sum_{i=1}^{N}\xi_{i} + \sum_{i=1}^{N}\alpha_{i}(1-\xi_{i}-y_{i}w \cdot x_{i} - y_{i}b) - \sum_{i=1}^{N} \mu_{i}\xi_{i} \quad s.t. \quad \alpha_{i} \geq 0, \quad \mu_{i} \geq 0  $$

接着求解拉格朗日对偶问题 $ \max_{\alpha, \mu} \min_{w,b,\xi} L(w,b,\xi, \alpha, \mu) $ 可得:

$$ \begin{align*}
&\min_{\alpha} \quad \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}(x_{i}\cdot x_{j}) - \sum_{i=1}^{N}\alpha_{i} \\
s.t. \quad & \sum_{i=1}^{N}\alpha_{i}y_{i}=0, \\
& 0 \leq \alpha_{i} \leq C, \quad i=1,2,...,N
\end{align*} $$

显而易见,其形式和线性可分支持向量机一致,只是约束条件变了, 同样可以利用SMO算法求解$\alpha^{\ast}$,进而解得$w^{\ast}$和$b_{s}^{\ast}$。

3.5 线性支持向量机与合页损失函数:

如3.4节所述:线性支持向量机,其模型为分离超平面$w^{\ast} \cdot x + b^{\ast} = 0$ 以及决策函数 $f(x) = \text{sign}(w^{\ast} \cdot x + b^{\ast})$,其学习策略为软间隔最大化。但其还有另一种解释,就是带有正则化项的合页损失函数最小化:

$$ \min_{w,b} \quad \sum_{i=1}^{N}\left [ 1-y_{i}(w \cdot x_{i} +b) \right ]_{+} +  \lambda \left \| w \right \|^2$$

上述式子中第一项$ [1-y(w \cdot x + b)]_{+} $为合页损失函数(hinge loss),第二项为参数$w$的L2正则化项。

令 $1-y_{i}(w \cdot x_{i} + b) = \xi_{i}, \quad \xi_{i} \geq 0$,且 $ \lambda = \frac{1}{2C}$,则:

$$ \min \quad \sum_{i=1}^{N} [1-y_{i}(w \cdot x_{i} +b)]_{+} + \lambda||w||^2 $$

$$ \Downarrow $$

$$ \min \quad \sum_{i=1}^{N} \xi_{i} + \lambda||w||^2 $$

$$ \Downarrow $$

$$ \min \quad \frac{1}{C}(\frac{1}{2}||w||^2 + C\sum_{i=1}^{N}\xi_{i} ) $$

可以看到其最小化的函数和3.4节中的一致。

3.6 非线性支持向量机与核函数:

非线性分类问题,是指通过利用非线性模型才能很好地进行分类的问题。求解非线性问题的一个方法是进行非线性变换,将非线性问题变换为线性问题,通过求解变换后的线性问题的方法求解原来的非线性问题。(类似于将二元的多项式回归转换成五元的线性回归)。总结一下,使用线性分类方法求解非线性分类问题分为两步:

第一步:使用一个变换将原空间的数据映射到新空间

第二步:在新空间里用线性分类学习方法从训练数据中学习分类模型

按照上述理论,我们定义一个从输入空间 $\mathcal{X}$ 到特征空间 $\mathcal{H}$ 的映射:$\phi(x): \mathcal{X} \rightarrow \mathcal{H}$,将这一映射应用到线性支持向量机的优化函数中,得到:

$$ \begin{align*}
&\min_{\alpha} \quad \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}\phi(x_{i})\cdot \phi(x_{j}) - \sum_{i=1}^{N}\alpha_{i} \\
s.t. \quad & \sum_{i=1}^{N}\alpha_{i}y_{i}=0, \\
& 0 \leq \alpha_{i} \leq C, \quad i=1,2,...,N
\end{align*} $$

但这样做还存在一个问题,那就是实例的内积 $\phi(x_{i}) \cdot \phi(x_{j})$ 是在映射后的空间$\mathcal{H}$中进行的,这是一个高维空间,计算内积并不容易且费时,于是,我们使用了核技巧,定义了核函数

$$ K(x,z) = \phi(x) \cdot \phi(z) $$

核技巧的想法是,在学习与预测中只定义核函数$K(x,z)$,而不显示地定义映射函数$\phi$。通常,计算$K(x,z)$比较容易,因为这是在低维空间上进行的。于是,应用了核函数之后的支持向量机的优化函数为:

$$ \begin{align*}
&\min_{\alpha} \quad \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_{i}\alpha_{j}y_{i}y_{j}K(x_{i},x_{j})- \sum_{i=1}^{N}\alpha_{i} \\
s.t. \quad & \sum_{i=1}^{N}\alpha_{i}y_{i}=0, \\
& 0 \leq \alpha_{i} \leq C, \quad i=1,2,...,N
\end{align*} $$

定义一个核函数,需要各种满足复杂的数学条件,一般我们使用的常见核函数包括:线性核函数(线性支持向量机中所使用的)、高斯核函数(也称作径向基核函数),其他还包括多项式核函数、Sigmoid核函数:

线性核函数:$ K(x,z) = x \cdot z $

高斯核函数:$ K(x,z) = \exp( -\gamma \| x-z \|^2 ) $

多项式核函数:$ K(x,z) = (\gamma x \cdot z + r)^d $

Sigmoid核函数:$ K(x,z) = \tanh( \gamma x \cdot z + r ) $

以上核函数中的参数 $\gamma$, $r$, $d$ 都需要根据实际情况,调参选取最优值。

 

4. 实战:

 sklearn中,其实有两个模块可以应用支持向量机,分别是 sklean.SVM,其中包括了线性 LinearSVC 以及 带核函数的 SVC;另一个是 sklearn.linear_model.SGDClassifier,只需要将loss设置为 ‘hinge’ 就等同于线性支持向量机。

以下代码中,我们分别使用了 LinearSVC(),SGDClassifier,以及 SVC(kernel='rbf') 进行模型训练,供参考:

  1 import pandas as pd
  2 import numpy as np
  3 import matplotlib.pyplot as plt
  4 from sklearn.preprocessing import MinMaxScaler, StandardScaler, OneHotEncoder, OrdinalEncoder
  5 from sklearn.impute import SimpleImputer
  6 from sklearn.model_selection import StratifiedKFold, GridSearchCV
  7 from sklearn.pipeline import Pipeline, FeatureUnion
  8 from sklearn.linear_model import SGDClassifier
  9 from sklearn.svm import LinearSVC, SVC
 10 from sklearn.metrics import accuracy_score, precision_score, recall_score
 11 from sklearn.base import BaseEstimator, TransformerMixin
 12 
 13 
 14 class DataFrameSelector(BaseEstimator, TransformerMixin):
 15     def __init__(self, attribute_name):
 16         self.attribute_name = attribute_name
 17 
 18     def fit(self, x, y=None):
 19         return self
 20 
 21     def transform(self, x):
 22         return x[self.attribute_name].values
 23 
 24 
 25 # Load data
 26 data_train = pd.read_csv('train.csv')
 27 
 28 train_x = data_train.drop('Survived', axis=1)
 29 train_y = data_train['Survived']
 30 
 31 # Data cleaning
 32 cat_attribs = ['Pclass', 'Sex', 'Embarked']
 33 dis_attribs = ['SibSp', 'Parch']
 34 con_attribs = ['Age', 'Fare']
 35 
 36 # encoder: OneHotEncoder()、OrdinalEncoder()
 37 cat_pipeline = Pipeline([
 38     ('selector', DataFrameSelector(cat_attribs)),
 39     ('imputer', SimpleImputer(strategy='most_frequent')),
 40     ('encoder', OneHotEncoder()),
 41 ])
 42 
 43 dis_pipeline = Pipeline([
 44     ('selector', DataFrameSelector(dis_attribs)),
 45     ('scaler', StandardScaler()),
 46     ('imputer', SimpleImputer(strategy='most_frequent')),
 47 ])
 48 
 49 con_pipeline = Pipeline([
 50     ('selector', DataFrameSelector(con_attribs)),
 51     ('scaler', StandardScaler()),
 52     ('imputer', SimpleImputer(strategy='mean')),
 53 ])
 54 
 55 full_pipeline = FeatureUnion(
 56     transformer_list=[
 57         ('con_pipeline', con_pipeline),
 58         ('dis_pipeline', dis_pipeline),
 59         ('cat_pipeline', cat_pipeline),
 60     ]
 61 )
 62 
 63 train_x_cleaned = full_pipeline.fit_transform(train_x)
 64 
 65 cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
 66 
 67 # test1:using Linear SVC
 68 clf1 = LinearSVC(tol=1e-4, max_iter=10000)
 69 param_grid = [{
 70     'penalty': ['l1'],
 71     'loss':['squared_hinge'],
 72     'dual':[False],
 73     'C':[1e-4, 1e-3, 1e-2, 1e-1, 1, 10],
 74     'class_weight':['balanced', None]
 75                 },
 76                 {
 77     'penalty': ['l2'],
 78     'loss':['hinge', 'squared_hinge'],
 79     'dual':[True],
 80     'C':[1e-4, 1e-3, 1e-2, 1e-1, 1, 10],
 81     'class_weight':['balanced', None]
 82                 }]
 83 
 84 grid_search = GridSearchCV(clf1, param_grid=param_grid, cv=cv, scoring='accuracy', n_jobs=-1, return_train_score=True)
 85 
 86 grid_search.fit(train_x_cleaned, train_y)
 87 predicted_y = grid_search.predict(train_x_cleaned)
 88 
 89 df_cv_results = pd.DataFrame(grid_search.cv_results_)
 90 print('-------Result of LinearSVC-------')
 91 print(grid_search.best_params_)
 92 print(accuracy_score(train_y, predicted_y))
 93 print(precision_score(train_y, predicted_y))
 94 print(recall_score(train_y, predicted_y))
 95 
 96 # test2: Using SGDClassifier with hinge loss / squared hinge loss
 97 clf2 = SGDClassifier(tol=1e-4, max_iter=10000)
 98 param_grid2 = [{
 99     'loss': ['hinge', 'squared_hinge'],
100     'penalty': ['l2', 'l1'],
101     'alpha': [1e-4, 1e-3, 1e-2, 1e-1, 1, 10, 100, 1000],
102     'class_weight':[None, 'balanced']
103                 }]
104 
105 grid_search2 = GridSearchCV(clf2, param_grid=param_grid2, cv=cv, scoring='accuracy', n_jobs=-1, return_train_score=True)
106 
107 grid_search2.fit(train_x_cleaned, train_y)
108 predicted_y2 = grid_search2.predict(train_x_cleaned)
109 
110 df_cv_results2 = pd.DataFrame(grid_search2.cv_results_)
111 print('-------Result of SGD-------')
112 print(grid_search2.best_params_)
113 print(accuracy_score(train_y, predicted_y2))
114 print(precision_score(train_y, predicted_y2))
115 print(recall_score(train_y, predicted_y2))
116 
117 # test3: Using SVC
118 clf3 = SVC(kernel='rbf', tol=1e-4)
119 param_grid3 = [{
120     'C': [0.1, 0.5, 0.75, 1, 2.5, 5, 10],
121     'gamma': ['scale', 'auto', 2e-4, 2e-3, 2e-2]
122                 }]
123 
124 grid_search3 = GridSearchCV(clf3, param_grid=param_grid3, cv=cv, scoring='accuracy', n_jobs=-1, return_train_score=True)
125 
126 grid_search3.fit(train_x_cleaned, train_y)
127 predicted_y3 = grid_search3.predict(train_x_cleaned)
128 
129 df_cv_results3 = pd.DataFrame(grid_search3.cv_results_)
130 print('-------Result of SVC with rbf-------')
131 print(grid_search3.best_params_)
132 print(accuracy_score(train_y, predicted_y3))
133 print(precision_score(train_y, predicted_y3))
134 print(recall_score(train_y, predicted_y3))
135 
136 # 导入预测数据,预测结果,并生成csv文件
137 data_test = pd.read_csv('test.csv')
138 submission = pd.DataFrame(columns=['PassengerId', 'Survived'])
139 submission['PassengerId'] = data_test['PassengerId']
140 
141 test_x_cleaned = full_pipeline.fit_transform(data_test)
142 
143 submission_LinearSVC = pd.DataFrame(submission, copy=True)
144 submission_LinearSVC['Survived'] = pd.Series(grid_search.predict(test_x_cleaned))
145 
146 submission_SVCrbf = pd.DataFrame(submission, copy=True)
147 submission_SVCrbf['Survived'] = pd.Series(grid_search3.predict(test_x_cleaned))
148 
149 submission_LinearSVC.to_csv('submission_LinearSVC.csv', index=False)
150 submission_SVCrbf.to_csv('submission_SVCrbf.csv', index=False)

 

4.1 结果分析:

和前几篇一样,分别将 LinearSVC() 和 SVC(kernel='rbf')的最优参数用于预测集,并将结果上传kaggle,结果如下:

可以看到线性SVM和其他线性方法的结果差不多;而使用了核函数后,结果确实有了一定的提升。

 

训练集

accuracy

训练集

precision

训练集

recall

预测集

accuracy(需上传kaggle获取结果)

朴素贝叶斯最优解 0.790 0.731 0.716 0.756
感知机 0.771 0.694 0.722 0.722
逻辑回归 0.807 0.781 0.690 0.768
线性SVM 0.801 0.772 0.684 0.773
rbf核SVM 0.834 0.817 0.731 0.785
posted @ 2021-01-10 16:16  古渡镇  阅读(1327)  评论(0)    收藏  举报