机器学习-吴恩达机器学习之支持向量机

Support Vector Machine

导入库函数

1
2
3
4
5
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
from sklearn import svm

Example Dataset 1

画出含有两个特征的样本集的线性边界函数。

数据处理

1
2
3
data=loadmat('ex6data1.mat')
X=data['X']
y=data['y'].flatten()

不知道标签的话可以查看

1
print(data.keys()) # 用于查看标签名称
  • 将数据可视化以便观察,注意要把正负样本分开可视化。
1
2
3
4
5
6
7
8
#==================================观察数据
def plot_data(X,y):
positive=X[y==1]
negative=X[y==0]

fig,ax=plt.subplots(figsize=(8,5))
plt.scatter(positive[:,0],positive[:,1],marker='+',label='positive')
plt.scatter(negative[:,0],negative[:,1],color='red',label='negative')

SVM拟合

可以通过改变c的大小来观察决策边界的变换。C的大小影响着模型对异常样本的反应。

因为这里是线性分类,所以使用线性核函数,参数kernel='linear'。使用sklearn中的svm.SVC()来拟合,其结果返回一个分类器对象。最后还需要用clf.fit(X,y)来拟合出最终模型。

1
2
3
c=1
clf=svm.SVC(c,kernel='linear') # 参数 c,kernel 返回一个分类器对象
clf.fit(X,y) # 用训练数据拟合分类器模型

可视化决策边界

np.meshgrid()生成网格点,再对每个网格点进行预测,最后画出等高线图,即决策边界。

关于np.meshgrid()可以参考 Numpy中的Meshgrid

1
2
3
4
5
6
7
8
9
10
#=============================================可视化决策边界
def visualize_boundary(clf,X):
x_min,x_max=X[:,0].min()*1.2,X[:,0].max()*1.1
y_min,y_max=X[:,1].min()*1.2,X[:,1].max()*1.1
xx,yy=np.meshgrid(np.arange(x_min,x_max,0.02),np.arange(y_min,y_max,0.02)) # 画网格点
Z=clf.predict(np.c_[xx.ravel(),yy.ravel()]) # 用训练好的分类器对网格点进行预测

Z=Z.reshape(xx.shape) # 转换成对应的网格点
plt.contour(xx,yy,Z,level=[0],colors='black') # 等高线图,画出0/1分界线
plt.show()

像前面提到的,不同的C值对决策边界会产生不同影响

c=1时:

EKmvo6.md.png

c=1000时:

EKno7t.md.png

注意左上角的正样本,c较大时,决策边界会过于追求将数据正确分类,而失去大间距的特点。

SVM with Gaussion Kernels

Gaussion Kernel

用SVM做分线性分类,我们需要用到高斯核函数。

  • 公式:

EKKnMQ.md.png

  • 代码:
1
2
3
#=============================================高斯核函数
def gaussKernel(x1,x2,sigma):
return np.exp(-((x1-x2)**2).sum()/(2*sigma**2))

Example Dataset 2

  • 数据处理及可视化
1
2
3
data2=loadmat('ex6data2.mat')
X2=data2['X']
y2=data2['y'].flatten()

EKKoJf.md.png

  • 用高斯核函数拟合模型
1
2
3
4
sigma=0.1
gamma=np.power(sigma,-2)/2
clf=svm.SVC(c,kernel='rbf',gamma=gamma) # 注意这里的参数gamma是整个分母,且要写成乘法形式
clf.fit(X2,y2)

kernel='rbf'代表使用高斯核函数,其中gamma值就是公式中的整个分母项,即$\frac{1}{2\sigma^{2} }$。

  • 决策边界
1
2
plot_data(X2,y2)
visualize_boundary(clf,X2)

EKJ52q.md.png

Example Dataset 3

  • 数据处理即可视化
1
2
3
data3=loadmat('ex6data3.mat')
X3=data3['X']
y3=data3['y'].flatten()

EKGYkj.md.png

可以发现,有个别样本存在比较大的差异。

  • 使用带高斯核函数的SVM进行训练
1
2
clf=svm.SVC(c,kernel='rbf',gamma=gamma)
clf.fit(X3,y3)
  • 决策边界
1
2
plot_data(X3,y3)
visualize_boundary(clf,X3)

EKGHNd.md.png

Spam Classification

建立一个垃圾邮件分类器,下面这个例子将会告诉你如何通过一封邮件来建立特征向量。

导入库函数

1
2
3
4
5
6
7
8
9
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.io import loadmat
from sklearn import svm
import re # 电子邮件处理的正则表达式

# 一个英文分词算法(Poter stemmer)
import nltk, nltk.stem.porter

Preprocesssing Email

  • 读取数据
1
2
with open('emailSample1.txt','r') as f:
email=f.read()

打印邮件内容为

1
2
3
4
5
6
7
8
9
> Anyone knows how much it costs to host a web portal ?
>
Well, it depends on how many visitors you're expecting.
This can be anywhere from less than 10 bucks a month to a couple of $100.
You should checkout http://www.rackspace.com/ or perhaps Amazon EC2
if youre running something big..

To unsubscribe yourself from this mailing list, send an email to:
groupname-unsubscribe@egroups.com

一般邮件都具有一些相似的内容,比如数字、URL、其它邮件地址。因此我们会采取一些”标准化“的方法来处理邮件,这些方法会提高垃圾邮件分类的性能。

例如:

1
2
3
4
5
6
7
8
1. Lower-casing: 把整封邮件转化为小写。
2. Stripping HTML: 移除所有HTML标签,只保留内容。
3. Normalizing URLs: 将所有的URL替换为字符串 “httpaddr”.
4. Normalizing Email Addresses: 所有的地址替换为 “emailaddr”
5. Normalizing Dollars: 所有dollar符号($)替换为“dollar”.
6. Normalizing Numbers: 所有数字替换为“number”
7. Word Stemming(词干提取): 将所有单词还原为词源。例如,“discount”, “discounts”, “discounted” and “discounting”都替换为“discount”。
8. Removal of non-words: 移除所有非文字类型,所有的空格(tabs, newlines, spaces)调整为一个空格.
  • 邮件内容处理:
1
2
3
4
5
6
7
8
9
10
11
def process_email(email):
"""做除了Word Stemming和Removal of non-words的所有处理"""
email = email.lower()
email = re.sub('<[^<>]>', ' ', email)
# 匹配<开头,然后所有不是< ,> 的内容,知道>结尾,相当于匹配<...>
email = re.sub('(http|https)://[^\s]*', 'httpaddr', email )
# 匹配//后面不是空白字符的内容,遇到空白字符则停止
email = re.sub('[^\s]+@[^\s]+', 'emailaddr', email)
email = re.sub('[\$]+', 'dollar', email)
email = re.sub('[\d]+', 'number', email)
return email

再接下来提取词干,去除非字符内容,并返回一个单词列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def email2TokenList(email):
"""预处理数据,返回一个干净的单词列表"""

# I'll use the NLTK stemmer because it more accurately duplicates the
# performance of the OCTAVE implementation in the assignment
stemmer = nltk.stem.porter.PorterStemmer()

email = process_email(email)

# 将邮件分割为单个单词,re.split() 可以设置多种分隔符
tokens = re.split('[ \@\$\/\#\.\-\:\&\*\+\=\[\]\?\!\(\)\{\}\,\'\"\>\_\<\;\%]', email)

# 遍历每个分割出来的内容
tokenlist = []
for token in tokens:
# 删除任何非字母数字的字符
token = re.sub('[^a-zA-Z0-9]', '', token);
# Use the Porter stemmer to 提取词根
stemmed = stemmer.stem(token)
# 去除空字符串‘’,里面不含任何字符
if not len(token): continue
tokenlist.append(stemmed)

return tokenlist

Vocabulary List

我们得到了邮件的单词列表,接下来需要结合记录实际中经常使用到的单词的词汇表vocab.txt。函数返回邮件单词在词汇表中的索引值。

1
2
3
4
5
def email2VocabIndices(email, vocab):
"""提取存在单词的索引"""
token = email2TokenList(email)
index = [i for i in range(len(vocab)) if vocab[i] in token]
return index

得到的索引值如下:

EK0An1.png

Extracting Feature from Emails

如果邮件中的单词出现在词汇表的第i个位置,则把特征向量的第i个索引值置为1,;如果没出现,置为0。也就是说,建立一个和词汇表同维度的向量feature,再把上面得到的索引位置的值改写为1,其余为0。可以得到:

EKcP5q.png

1
2
3
4
5
6
7
8
9
def email_feature_vector(email):
'''将email的单词转换为特征向量0/1'''
df = pd.read_table('vocab.txt', names=['words'])
vocab = df.values # Datafram转换为ndarray
vector = np.zeros(len(vocab))
vecab_indices = email2VocabIndices(email, vocab)
for i in vecab_indices:
vector[i] = 1
return vector

该向量长度为1899,其中有45个索引值为1。

Training SVM for Spam Classification

用已经预处理过的训练集和测试集来拟合模型,并计算准确度。

1
2
3
clf = svm.SVC(C=0.1, kernel='linear')
clf.fit(X, y)
print(clf.score(X,y),clf.score(Xtest,ytest))

打印结果:

1
0.99825 0.989

Top predictors for Spam

返回权重最大的15个单词,这些单词出现频率高的邮件就是垃圾邮件。

1
2
3
4
5
6
7
8
9
def get_vocab_list():
'''以字典形式获得词汇表'''
vocab_dict = {}
with open('vocab.txt') as f: #打开txt格式的词汇表
for line in f:
(val, key) = line.split() #读取每一行的键和值
vocab_dict[int(val)] = key #存放到字典中

return vocab_dict

获取词汇表

1
2
3
4
5
6
vocab_list = get_vocab_list()  #得到词汇表 存在字典中
indices = np.argsort(clf.coef_).flatten()[::-1] #对权重序号进行从大到小排序 并返回

for i in range(15): #打印权重最大的前15个词 及其对应的权重
print('{} ({:0.6f})'.format(vocab_list[indices[i]],
clf.coef_.flatten()[indices[i]]))

最终打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
otherwis (0.500614)
clearli (0.465916)
remot (0.422869)
gt (0.383622)
visa (0.367710)
base (0.345064)
doesn (0.323632)
wife (0.269724)
previous (0.267298)
player (0.261169)
mortgag (0.257298)
natur (0.253941)
ll (0.253467)
futur (0.248297)
hot (0.246404)

参考资料

[1] 机器学习 | 吴恩达机器学习第七周编程作业(Python版)

[2] 吴恩达机器学习作业Python实现(六):SVM支持向量机

这次的代码几乎完全照抄两位大神的,实在是正则化的内容不懂,而因为用了sklearn库使得拟合变得又很简单。记录一下自己的naive,还得努力。

-------------End-------------
梦想总是要有的,万一有人有钱呢?