吴恩达机器学习第六次编程作业-支持向量机

编程作业:支持向量机


工具:Python3.5,Pycharm2019.1
参考资料:

  1. numpy元素的区间查找
  2. 各种核函数
  3. python中支持向量机SVM的使用
  4. 吴恩达机器学习作业:SVM支持向量机
  5. 支持向量机SVM和人工神经网络ANN的比较
  6. 我是怎样理解支持向量机(SVM)与神经网络的
  7. python txt文件常用读写操作
  8. Python3中字符串、列表、数组的转换方法
  9. Python 输出百分比的两种方式
  10. Python 输出对齐

SVM with Gaussian Kernels—data3 完整代码
Spam Classification完整代码


任务: 使用support vector machines (SVMs)建立一个垃圾邮件分类器。

Support Vector Machines

总体分两个模块:

  1. 直觉感受是SVM如何工作的并且了解高斯核函数和SVM是如何一起工作的
  2. 使用支持向量机建立一个垃圾邮件分类器
绘制样本数据集1
1
2
3
4
5
6
7
8
9
10
11
12
def Get_Data():
data=sio.loadmat("./ex6/ex6data1.mat")
# for key in data:
# print(key)
return data

def Plot_Data(data): # 提取数据
   data1=data['X'][np.where(data['y'].ravel()==1)] # positive examples
data2=data['X'][np.where(data['y'].ravel()==0)]
plt.plot(data1[:,0],data1[:,1],'b+')
plt.plot(data2[:,0],data2[:,1],'yo')
   plt.show()

Figure_19.png
有一个极端样本数据大概在 (0.1, 4.1)位置。
当然,设置坐标轴范围可以使得更加贴合文档

训练SVM并尝试不同参数C

文档是直接调用已写好的函数包,这里由于在linux环境下使用python,在sklearn库中也有SVM的封装。代码见下,详解见后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def Train_SVM(data,X,y):
classifier = svm.SVC(C=1, kernel='linear', gamma='auto_deprecated', decision_function_shape='ovo')
classifier.fit(X, y.ravel())
   x1 = np.linspace(min(X[:, 0]), max(X[:, 0]), 100) # 第一特征,可以各多选取一些样本点,使得决策线更清晰
   x2 = np.linspace(min(X[:, 1]), max(X[:, 1]), 100)
x1, x2 = np.meshgrid(x1, x2)
# print(x1.shape,x2.shape) # 100*100
grid = np.stack((x1.flat, x2.flat), axis=1)
# print(grid.shape) # 10000*2
grid_predict = classifier.predict(grid)
grid_predict = grid_predict.reshape(x1.shape) # 还原成网格形状
# plt.xlim(0, 4.5)
 # plt.ylim(1.5, 5)
plt=Plot_Data(data)
plt.contour(x1, x2, grid_predict)
plt.show()

尝试C=1,100的效果分别如图:
Figure_20.png
Figure_21.png
关于以上代价解释:

  • kernel为核函数,可选[‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’ ], kernel=’rbf’时(default),为高斯核。
  • C越大分类效果越好,但可能过拟合(default C=1.0)
  • gamma值越小,分类界面越连续;gamma值越大,分类界面越“散”,分类效果越好,但有可能会过拟合。
  • decision_function_shape为分类器,可选[‘ovo’,’ovr’]分别表示一对一分类器,一对多分类。默认为’ovr’,’one vs rest’。
  • meshgrid表示生成网格
  • 图片中决策线比较粗糙,在绘制决策线的时候两个特征各多取100个样本点在计算网格交叉就行

SVM with Gaussian Kernels

任务:使用高斯核的SVM来做非线性回归。
步骤一: 实现高斯核函数,定义如下
2019-09-07 09-36-00 的屏幕截图.png

1
2
3
def GaussianKernel(x1,x2,sigma): # 求向量x1与向量x2的相似度
sim=math.exp(-((x1-x2)**2).sum()/(2*sigma*sigma))
return sim

步骤二: 可视化样本数据集二
步骤三:使用高斯核函数训练SVM并绘制图像,与步骤二合并
代码和线形核函数差不多,将’linear’改为’rbf’,gamma=30,’ovo’改为’ovr’。这里默认C=1,后面改变C的值观察分类效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def Plot_Boundary(X,classifier):
x1 = np.linspace(min(X[:, 0]), max(X[:, 0]), 500) # 第一特征
x2 = np.linspace(min(X[:, 1]), max(X[:, 1]), 500)
x1, x2 = np.meshgrid(x1, x2)
# print(x1.shape,x2.shape) # 100*100
grid = np.stack((x1.flat, x2.flat), axis=1)
# print(grid.shape) # 10000*2
grid_predict = classifier.predict(grid)
grid_predict = grid_predict.reshape(x1.shape) # 还原成网格形状
plt = Plot_Data(data)
# plt.xlim(0, 4.5)
# plt.ylim(1.5, 5)
plt.contour(x1, x2, grid_predict)
  # plt.title('SVM Decision Boundary with C = 100 ', fontsize=12)
plt.show()

''' Training Linear SVM(try C=1,100) '''
def Train_SVM(data,X,y):
classifier = svm.SVC(C=100, kernel='rbf', gamma=30, decision_function_shape='ovr')
classifier.fit(X, y.ravel())
Plot_Boundary(X,classifier)

Figure_22.png
Figure_23.png
Figure_24.png
可以看出当C=1时,拟合不足,当C=100时有些过拟合,当C =10,gamma=50即np.pow(0.1,-2)/2的时候效果较好。gamma效果和C类似。

步骤四:更进一步,使用样本数据集三,用训练样本X训练SVM,交叉验证集Xval并找到最适合的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def Find_Best_Param(X,y,Xval,yval):
_C=0;_sigma=0;_predict=0.0
for C in [0.01,0.03,0.1,0.3,1,3,10,30]:
for sigma in [0.01,0.03,0.1,0.3,1,3,10,30]:
classifier=svm.SVC(C=C,kernel='rbf',gamma=np.power(sigma,-2.0)/2,decision_function_shape='ovr')
classifier.fit(X,y)
predict=classifier.score(Xval,yval) # Returns the mean accuracy on the given test data and labels
# print(C, sigma, predict)
if predict>_predict:
_predict,_C,_sigma=predict,C,sigma
return _C,_sigma,_predict

C,sigma,prediction=Find_Best_Param(X,y.ravel(),data['Xval'],data['yval'].ravel())
print(C,sigma,prediction) # expect to see: 1 0.1 0.965
Train_SVM(data,X,y,C,sigma)

Figure_25.png
由于在编程的时候碰到一些问题,解释如下:

  • .score()可以查看手册,在参考资料中依葫芦画瓢
  • pow()函数最好用实数,整数可能报错
  • 将可能的C及σ用列表或元组[]存起来遍历,如果用{},会忽略某些元素要和C语言区分开
  • 此处完整代码见顶,其余模块的完整代码大同小异

Spam Classification

任务:训练一个分类器来辨别一封邮件x是否为垃圾邮件(y=0),需要从邮件中提取特征建立特征向量。

步骤一: 邮件预处理
2019-09-09 15-12-31 的屏幕截图.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def Process_Email(file_contents):
file_contents = file_contents.lower() # Lower case
file_contents=re.sub(r'<[^<>]>',' ',file_contents) # <...>
file_contents=re.sub('(http|https)://[^\s]*','heepaddr',file_contents) # Handle URLS
file_contents=re.sub('[^\s]+@[^\s]+', 'emailaddr', file_contents) # andle Email Addresses
file_contents=re.sub('[0-9]+','number',file_contents) # Handle Numbers
file_contents=re.sub('[$]+','dollar',file_contents) # Handle $ sign

stemmer = nltk.stem.porter.PorterStemmer()
tokens = re.split('[ \@\$\/\#\.\-\:\&\*\+\=\[\]\?\!\(\)\{\}\,\'\"\>\_\<\;\%]', file_contents)
tokenlist=[]
for token in tokens:
token = re.sub('[^a-zA-Z0-9]', '', token) # 删除任何非字母数字的字符
stemmed = stemmer.stem(token) # Use the Porter stemmer to 提取词根
if len(token)==0:continue
       tokenlist.append(stemed)
print(token,end=" ")
return tokenlist

关于以上代码:

  1. 涉及到Python正则表达式,这个尚处与知识盲区
  2. 大体思路是整体变小写,去除HTML标签,换掉数字地址和邮箱等,换掉各种符号。最后分割成单词列表,每个单词去除特殊符号再提取词根
  3. 参考资料顶部已给出

步骤二:将单词映射成数字,单词表中已给出映射关系,然后新建向量,单词列表中的单词在邮件中出线过则为1,否则为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def TransForm(token,vocabList):
# print(type(vocabList)) # <class 'list'>
# index = [i for i in range(len(vocabList)) if vocabList[i] in token] # except to see: 62 45 1899
index = [vocabList.index(x) for x in token if vocabList.count(x) ] # except to see: 62 53 1899
print(len(token),len(index),len(vocabList))
return index

def Extracting_Features(word_index,vocabList):
vector=np.zeros(len(vocabList))
vector[[i for i in word_indices]]=1
return vector

file_contents=Read_File('./ex6/emailSample1.txt')
vocabList=Read_File('./ex6/vocab.txt')
vocabList = vocabList.split() # default ' ' as split sign
for x in vocabList: # 去除数字
   if x[0]>='0' and x[0]<='9':vocabList.remove(x)
word_indices=TransForm(Process_Email(file_contents),vocabList)
features=Extracting_Features(word_indices,vocabList)

Training SVM for Spam Classification

使用所给的邮件特征向量进行训练和测试SVM,文件分别为: spamTrain.mat,spamTest.mat

1
2
3
4
5
6
7
8
9
def Train_and_Test_SpamClass():
spam_train, X_train, y_train = Get_Data('./ex6/spamTrain.mat')
spam_test=sio.loadmat('./ex6/spamTest.mat') # ,X_test,y_test
X_test,y_test=spam_test['Xtest'],spam_test['ytest']
print('(this may take 1 to 2 minutes) ...')
classifier = Train_SVM(spam_train, X_train, y_train, C=0.1, sigma=0.1)
   print('Training Accuracy: {:.2%}'.format(classifier.score(X_train, y_train))) # (this may take 1 to 2 minutes) ...
Training Accuracy: 99.83%
   print('Test Accuracy: {:.2%}' .format(classifier.score(X_test,y_test))) # Test Accuracy: 98.90%

预测邮件

根据emailSample1.txt和emailSample2.txt提取的特征向量进行预测此邮件是否为垃圾邮件。

1
2
3
4
5
6
7
8
9
10
def Predict_email(classifier,email):
X=Preprocessing_Emails(email)
return classifier.predict(X.reshape(1,-1))

path=['./ex6/emailSample1.txt','./ex6/emailSample2.txt','./ex6/spamSample1.txt','./ex6/spamSample2.txt']
classifier=Train_and_Test_SpamClass()
for Sample_path in path:
file_contents = Read_File(Sample_path)
print('%-23s: %2s' %(Sample_path,Predict_email(classifier,file_contents)))
print('1 indicates spam, 0 indicates not spam')

输出如下:

1
2
3
4
5
6
(this may take 1 to 2 minutes) ...
./ex6/emailSample1.txt : [0]
./ex6/emailSample2.txt : [0]
./ex6/spamSample1.txt : [1]
./ex6/spamSample2.txt : [1]
1 indicates spam, 0 indicates not spam