前言:
通俗来讲,所谓支持向量机是一种分类器,对于做出标记的两组向量,给出一个最优分割超曲面把这两组向量分割到两边,使得两组向量中离此超平面最近的向量(即所谓支持向量)到此超平面的距离都尽可能远。
当一个分类问题, 数据是线性可分的, 也就是用一根棍就可以将两种小球分开的时候, 我们只要将棍的位置放在让小球距离棍的距离最大化的位置即可, 寻找这个最大间隔的过程, 就叫最优化. 但是, 一般的数据是线性不可分的, 也就是找不到一个棍将两种小球很好的分类. 这个时候, 我们就需要像大侠一样, 将小球拍起, 用一张纸代替小棍将小球进行分类. 想要让数据飞起, 我们需要的东西就是核函数 (kernel) , 用于切分小球的纸, 就是超平面 (hyperplane) . 如果数据是N维的, 那么超平面就是N-1维. (补: 未使用核函数情况下)
把一个数据集正确分开的超平面可能有多个, 而那个具有“最大间隔”的超平面就是SVM要寻找的最优解. 而这个真正的最优解对应的两侧虚线所穿过的样本点, 就是SVM中的支持样本点, 称为支持向量(support vector). 支持向量到超平面的距离被称为间隔 (margin) .
一、算法概述
1.线性可分
1.1超平面
我们希望寻找到这样的直线,使得距离这条直线最近的点到这条直线的距离最短。
我们从如下图直观来解释这一句话就是要求的两条外面的线之间的间隔最大。
假如数据样本是随机出现的,那么这样分割之后数据点落入到其类别一侧的概率越高那么最终预测的准确率也会越高。
在高维空间中这样的直线称之为超平面,因为当维数大于三的时候我们已经无法想象出这个平面的具体样子。
那些距离这个超平面最近的点就是所谓支持向量,实际上如果确定了支持向量也就确定了这个超平面,找到这些支持向量之后其他样本就不会起作用了。
基本模型是定义在特征空间上的间隔最大的先行分类器,目标是找到一个决策边界(超平面),使得离超平面最近的点到超平面的距离越远越好。例如下图,3条线都可以将两类数据分开,如何选择“最好的”一条线,是支持向量机(SVM)算法思想的核心。
超平面:如果空间是3维的,那么它的超平面是2维平面,而如果空间是2维的,则其超平面是1维线。
1.2最大间隔
最大间隔,是指在特征空间中找到的能够将不同类别实例分开的决策边界(超平面),并且这个决策边界到两个类别中最靠近的样本点的距离是最大的。
支持向量机的核心思想是间隔最大化,最不受噪声的干扰。
1.3对偶问题
通常我们需要求解的最优化问题有如下几类:
(1)无约束优化问题,可以写为:min f(x)
(2)有等式约束的优化问题,可以写为:
min f(x),
s.t. h_i(x) = 0;i =1, ..., n
(3)有不等式约束的优化问题,可以写为:
min f(x),
s.t. g_i(x) <= 0;i =1, ..., n
h_j(x) = 0;j =1, ..., m
2.线性不可分
2.1核函数
核函数(Kernel Function)允许SVM在原始特征空间中无法线性分割的情况下,通过映射数据到高维空间来实现线性分割。核函数的基本思想是在新的高维空间中找到一个更容易线性分割的超平面。
在SVM中,核函数可以将输入特征映射到一个更高维度的空间,从而使得在原始特征空间中非线性可分的问题在新的空间中变得线性可分。这样,SVM就能够通过一个超平面来分割数据。
基本想法:不显式地设计核映射, 而是设计核函数.
常见的核函数:
二、SVM实现垃圾邮件分类
词汇表
为了构建一个合适的邮件分类器,需要选择一些使用频率最高的词汇,(如果选择一些使用频率很小的词汇,可能会导致过拟合问题)。根据在垃圾邮件语料库中至少出现了100次以上的单词可以添加到词汇表中的要求,最终,词汇表中有1899个单词。在实践中,一个词汇表中通常有10000到50000个单词。
有了词汇表,可以将预处理的电子邮件中的每个单词映射到包含词汇表中单词的索引的单词索引列表。所谓单词索引列表就是每个单词所对应的数字索引所组成的列表
以上过程用python实现如下所示:
def process_email(email_contents):
vocab_list = get_vocab_list()
word_indices = np.array([], dtype=np.int64)
email_contents = email_contents.lower()
email_contents = re.sub('<[^<>]+>', ' ', email_contents)
email_contents = re.sub('[0-9]+', 'number', email_contents)
email_contents = re.sub('(http|https)://[^\s]*', 'httpaddr', email_contents)
email_contents = re.sub('[^\s]+@[^\s]+', 'emailaddr', email_contents)
email_contents = re.sub('[$]+', 'dollar', email_contents)
print('==== Processed Email ====')
stemmer = nltk.stem.porter.PorterStemmer()
tokens = re.split('[@$/#.-:&*+=\[\]?!(){\},\'\">_<;% ]', email_contents)
for token in tokens:
token = re.sub('[^a-zA-Z0-9]', '', token)
token = stemmer.stem(token)
if len(token) < 1:
continue
for i in range(1, len(vocab_list) + 1):
if vocab_list[i] == token:
word_indices = np.append(word_indices, i)
print(token)
print('==================')
return word_indices
def get_vocab_list():
vocab_dict = {}
with open('vocab.txt') as f:
for line in f:
(val, key) = line.split()
vocab_dict[int(val)] = key
return vocab_dict
plt.ion()
np.set_printoptions(formatter={'float': '{: 0.6f}'.format})
print('Preprocessing sample email (emailSample1.txt) ...')
file_contents = open('emailSample1.txt', 'r').read()
word_indices = process_email(file_contents)
提取邮件中的特征
有了以上过程,需要从文本邮件中得到特征向量
def email_features(word_indices):
n = 1899
features = np.zeros(n + 1)
features[word_indices - 1] = 1
return features
训练SVM算法
完成了邮件特征变量的提取之后,可以利用4000个训练样本和1000个测试样本训练SVM算法,每个原始的邮件将会被转化为向量(词汇表中有1899个词汇,会被添加到向量中,最后,得到的向量包含1900个数字)。载入数据集之后,用变量表示垃圾邮件,而0表示非垃圾邮件可就可以训练SVM算法了。具体实现代码如下所示:
data = scio.loadmat('spamTrain.mat')
X = data['X']
y = data['y'].flatten()
print('Training Linear SVM (Spam Classification)')
print('(this may take 1 to 2 minutes)')
c = 0.1
clf = svm.SVC(c, kernel='linear')
clf.fit(X, y)
p = clf.predict(X)
print('Training Accuracy: {}'.format(np.mean(p == y) * 100))
运行结果:
实验总结
SVM的优缺点:
优点:
可用于线性/非线性分类,也可以用于回归,泛化错误率低,也就是说具有良好的学习能力,且学到的结果具有很好的推广性。
可以解决小样本情况下的机器学习问题,可以解决高维问题,可以避免神经网络结构选择和局部极小点问题。
SVM是最好的现成的分类器,现成是指不加修改可直接使用。并且能够得到较低的错误率,SVM可以对训练集之外的数据点做很好的分类决策。
缺点:
计算开销较大: 在大规模数据集上训练SVM可能会变得计算开销很大。其时间复杂度主要取决于训练样本的数量。
对参数敏感: SVM的性能对参数的选择和调整相当敏感,需要仔细调参,而且不同类型的数据可能需要不同的核函数和参数。
只适用于二分类问题: SVM最初是为二分类问题设计的,而在数据挖掘的实际应用中,一般要解决多分类问题,但支持向量机对于多分类问题解决效果并不理想。
不适用于非线性大规模数据: 在非线性大规模数据集上,SVM的性能可能不如一些其他算法,因为它的训练时间复杂度较高。
不擅长处理缺失数据: SVM对于数据中的缺失值比较敏感,需要在训练前进行数据的处理和清洗。