目录
一、支持向量机介绍
1.1 支持向量机
支持向量机(SVM)是监督学习算法,属于二分类模型。其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。支持向量机的学习算法是求解凸二次规划的最优化算法。
在线性可分时,在原空间寻找两类样本的最优分类超平面。在线性不可分时,加入松弛变量并通过使用非线性映射将低纬度输入空间的样本映射到高纬度空间(升维)使其变为线性可分,就可以在该特征空间中寻找最优分类超平面。
1.2 超平面
在 n 维空间中,超平面是一个 n−1 维的子空间,该子空间可将 n 维空间分隔成为两部分。在二维和三维空间中超平面的几何表示比较直观,例如二维空间是一个面,那么其超平面就是一维的直线;在三维立体空间中,超平面是将立体分隔成两部分的二维平面。
实数域 n 维空间中的超平面定义如下:
二、线性可分
2.1 分隔超平面
二维空间中的一条直线方程 ,推广到n为空间,就变成了超平面方程,即
SVM算法的目的就是找到这样一个超平面,使得在超平面上方的点x代入函数 ,f(x) 全部大于0,在超平面下方的点代入函数
,f(x) 全部小于0,完成对正负样本的分类。
2.2 最大间隔
最大间隔,是指在特征空间中找到的能够将不同类别实例分开的决策边界(超平面),并且这个决策边界到两个类别中最靠近的样本点的距离是最大的。
支持向量机的核心思想是间隔最大化,最不受噪声的干扰。
SVM划分的超平面:f(x) = 0,w为法向量,决定超平面方向
假设超平面将样本正确划分,则:
f(x) ≥ 1,y = +1
f(x) ≤ −1,y = −1
,r为分类间隔
2.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)类的优化问题,常常使用的方法就是拉格朗日乘子法(Lagrange Multiplier) ,即把等式约束h_i(x)用一个系数与f(x)写为一个式子,称为拉格朗日函数,而系数称为拉格朗日乘子。通过拉格朗日函数对各个变量求导,令其为零,可以求得候选值集合,然后验证求得最优值。
- KKT条件
对于第(3)类的优化问题,常常使用的方法就是KKT条件(Karush-Kuhn-Tucker)。同样地,我们把所有的等式、不等式约束与f(x)写为一个式子,也叫拉格朗日函数,系数也称拉格朗日乘子,通过一些条件,可以求出最优值的必要条件,这个条件称为KKT条件。
三、线性不可分
对于线性不可分的数据集,我们无法找到这样一种直线,将不同类型的样本分割开来。
Vapnik提出了一种观点,我们所认为的线性不可分,只是在当前维度下线性不可分,不代表它在高维空间中线性不可分。比如有一组样本在二维空间线性不可分,但是在三维空间中,我们是有可能找到这样一条直线将其分隔开来的,Vapnik还认为,当维数趋于无穷时,一定存在这样一条线,可以将不同类型的样本分割开来。
3.1 核函数
核函数使SVM在高维空间中进行非线性映射,将输入数据映射到更高维度的特征空间中,从而使得在该空间中的问题变得线性可分。核函数的作用在于计算两个样本之间的内积,而无需显式地计算映射后的高维空间中的坐标。
核函数只是将完全不可分问题,转换为可分或达到近似可分的状态。核函数方法处理非线性问题的基本思想:按一定的规则进行映射,使得原来的数据在新的空间中变成线性可分的,从而就能使用之前推导的线性分类算法进行处理。
常见的核函数
-
线性核函数(Linear Kernel):
。直接使用输入特征进行内积计算,适用于线性可分的情况。
-
多项式核函数(Polynomial Kernel):
,其中 d≥1 是多项式的次数, c 是常数。这个核函数可处理一些非线性关系,通过增加 d 的值,增加模型的复杂度。
-
高斯核函数(Gaussian Kernel):
,其中
>0是控制函数宽度的参数。可用于处理非线性关系。
-
Sigmoid核函数:
,其中
和
是参数,可用于处理非线性关系。
四、使用SVM实现垃圾邮件分类
读取电子邮件内容
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
f = open('data\emailSample1.txt')
email = f.read()
print(email)
对数据进行处理,提取单词
def get_vocab_list():
vocab_dict = {}
with open('data/vocab.txt') as f:
for line in f:
(val, key) = line.split()
vocab_dict[int(val)] = key
return vocab_dict
def process_email_1(email):
'''对邮件进行预处理(步骤1~8)'''
vocab_list = get_vocab_list()
word_indices = np.array([], dtype=np.int64)
email = email.lower() #把所有的大写字母转换成小写字母
email = re.sub(r'<[^<>]+>',' ',email) # 匹配<开头,然后所有不是< ,> 的内容,知道>结尾,相当于匹配<...>
email = re.sub(r'(http|https)://[^\s]*','httpaddr',email)
email = re.sub(r'[^\s]+@[^\s]+','emailaddr',email)
email = re.sub(r'[$]+','dollar',email)
email = re.sub(r'[0-9]+','number',email)
print('==== Processed Email ====')
stemmer = nltk.stem.porter.PorterStemmer() #调用词干提取的函数
#email = process_email_1(email) #对邮件进行预处理第一步
tokens = re.split(r'[@$/#.-:&*+=\[\]?!(){\},\'\">_<;% ]',email) #将邮件分割成单个单词,re.split()可以设置多种分隔符
tokenList = [] #设置一个存放提取内容的数组
for token in tokens: #遍历每一个分割出来的内容
token = re.sub('[^a-zA-Z0-9]+','',token) #删除非文字类型的字符
stemmed = stemmer.stem(token) #使用调用的函数提取词干
if not len(token): #用于去除空字符,里面不含任何字符
continue
tokenList.append(stemmed)
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 email2feature_vector(email):
'''把email转换为词向量的形式.将存在单词的相应位置的值设为1,不存在设为0.'''
df = pd.read_csv('data/vocab.txt',names=['words']) #读取.txt文件
vocab = df.values #返回数组
vector = np.zeros(len(vocab)) #初始化向量
vocab_indices = email2vocab_indices(email, vocab) #返回有单词的索引
for i in vocab_indices: #用于将存在单词的相应位置的值设置为1
vector[i] = 1
return vector
vector = email2feature_vector(email)
print('the feature vector had length {} and {} non-zero entries.'.format(len(vector), int(np.sum(vector))))
训练支持向量机
from sklearn import svm
mat_train = loadmat('data/spamTrain.mat')
X_train = mat_train['X']
y_train = mat_train['y'].flatten()
mat_test = loadmat('data/spamTest.mat')
X_test = mat_test['Xtest']
y_test = mat_test['ytest'].flatten()
C = 0.1
clf = svm.SVC(C, kernel='linear')
clf.fit(X_train, y_train)
p_train = clf.score(X_train, y_train)
p_test = clf.score(X_test, y_test)
print('Training Accuracy: {:.1%}'.format(p_train))
print('Test Accuracy: {:.1%}'.format(p_test))
运行结果如下:
Training Accuracy: 99.8%
Test Accuracy: 98.9%
得到训练集和测试集的精确度。
预处理和标准化操作:
Lower-casing:把整封邮件转化为小写。
Stripping HTML:移除所有HTML标签,只保留内容。
Normalizing URLs:将所有的URL替换为字符串 “httpaddr”。
Normalizing Email Addresses:所有的地址替换为 “emailaddr”。
Normalizing Dollars:所有dollar符号($)替换为“dollar”。
Normalizing Numbers:所有数字替换为“number”。
Word Stemming(词干提取):将所有单词还原为词源。例如,“discount”, “discounts”, “discounted” and “discounting”都替换为“discount”。
Removal of non-words:: 移除所有非文字类型,所有的空格(tabs, newlines, spaces)调整为一个空格。
五、SVM的优缺点
优点:
-
适用于高维空间: SVM在高维空间中表现良好,对于特征维度较高的数据集,仍能有效地进行分类。
-
非线性映射: 通过使用核函数,SVM可以处理非线性关系,将数据映射到更高维度的空间中,从而在原始空间中非线性可分的问题变得线性可分。
-
鲁棒性: SVM对于训练数据中的噪声相对不敏感,可以有效地处理一些异常值。
-
间隔最大化: SVM的目标是找到能够最大化类别之间间隔的超平面,这有助于提高模型的泛化能力。
-
核函数的灵活性: 可以根据问题选择不同的核函数,以适应不同类型的数据。
缺点:
-
计算开销较大: 在大规模数据集上训练SVM可能会变得计算开销很大。其时间复杂度主要取决于训练样本的数量。
-
对参数敏感: SVM的性能对参数的选择和调整相当敏感,需要仔细调参,而且不同类型的数据可能需要不同的核函数和参数。
-
只适用于二分类问题: SVM最初是为二分类问题设计的,而在数据挖掘的实际应用中,一般要解决多分类问题,但支持向量机对于多分类问题解决效果并不理想。
-
不适用于非线性大规模数据: 在非线性大规模数据集上,SVM的性能可能不如一些其他算法,因为它的训练时间复杂度较高。
-
不擅长处理缺失数据: SVM对于数据中的缺失值比较敏感,需要在训练前进行数据的处理和清洗。