实验指导书 下载密码:ag1v
本篇博客主要讲解,吴恩达机器学习第七周的编程作业,包含两个实验,一是线性svm和带有高斯核函数的svm的基本使用;二是利用svm进行垃圾邮件分类。原始实验使用Matlab实现,本篇博客提供Python版本。
目录
1.实验包含文件
文件名称 | 含义 |
ex6.py | 实验1主程序 |
ex6data1.mat | 实验1的数据集1 |
ex6data2.mat | 实验1的数据集2 |
ex6data3.mat | 实验1的数据集3 |
plotData.py | 可视化2D数据集 |
visualizeBoundary.py | 可视化决策边界 |
gaussianKernel.py | 高斯核函数 |
ex6_spam.py | 实验2垃圾邮件分类主程序 |
spamTrain.mat | 邮件训练集 |
spamTest.mat | 邮件测试集 |
spamSample1.txt | 垃圾邮件1例子 |
spamSample2.txt | 垃圾邮件2例子 |
vocab.txt | 词汇表 |
emailSample1.txt | 邮件1例子 |
emailSample2.txt | 邮件2例子 |
processEmail.py | 邮件预处理 |
emailFeatures.py | 从邮件中提取特征 |
完成红色部分程序的关键代码。
2.实验1 SVM简单使用
- 打开实验1主程序ex6.py
'''第1部分 加载并可视化数据集'''
print('Loading and Visualizing data ... ')
data = scio.loadmat('ex6data1.mat') #加载矩阵格式的数据集
X = data['X'] #提取原始输入特征矩阵
y = data['y'].flatten() #提取标签 并转换为1维数组
m = y.size #样本数
# 可视化训练集
pd.plot_data(X, y)
- 编写plotData.py
def plot_data(X, y):
plt.figure()
positive=X[y==1] #提取正样本
negtive=X[y==0] #提取负样本
plt.scatter(positive[:,0],positive[:,1],marker='+',label='y=1') #画出正样本
plt.scatter(negtive[:,0],negtive[:,1],marker='o',label='y=0') #画出负样本
plt.legend() #显示图例
- 数据集1可视化效果
数据集1只有两个原始输入特征,是线性可分的。
- 训练线性SVM
'''第2部分 训练SVM(线性核函数) 并可视化决策边界'''
print('Training Linear SVM')
#SVM的代价函数以及训练不用自己写 直接调用程序包
c = 1000 #SVM参数 可以通过改变他来观察决策边界的变化
clf = svm.SVC(c, kernel='linear', tol=1e-3) #声明一个线性SVM
clf.fit(X, y) #训练线性SVM
pd.plot_data(X, y) #可视化训练集
vb.visualize_boundary(clf, X, 0, 4.5, 1.5, 5) #可视化决策边界
- 查看可视化决策边界的程序visualizeBoundary.py
def visualize_boundary(clf, X, x_min, x_max, y_min, y_max): #x,y轴的取值范围
h = .02
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))#在x,y轴上以0.02为间隔,生成网格点
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])#预测每个网格点的类别0/1
Z = Z.reshape(xx.shape) #转型为网格的形状
plt.contour(xx, yy,Z, level=[0],colors='r') #等高线图 将0/1分界线(决策边界)画出来
- 查看决策边界
当参数C=1000比较大时,可以让我们对大间隔有一个直观的理解。但是如果训练集中存在异常点,C很大时模型会对异常点敏感,高方差(过拟合),如下所示:
这种情况下,应该把C调的小一些,此次会忽略异常点的影响,得到一个大间距的决策边界。
当C=10时:
当C=1时:
但不要把C调的太小,否则会出现高偏差(欠拟合).
- 利用高斯核函数计算向量相似度
'''第3部分 实现高斯核函数'''
print('Evaluating the Gaussian Kernel')
x1 = np.array([1, 2, 1])
x2 = np.array([0, 4, -1])
sigma = 2 #高斯核函数参数
sim = gk.gaussian_kernel(x1, x2, sigma) #利用高斯核函数计算两个向量的相似度
print('Gaussian kernel between x1 = [1, 2, 1], x2 = [0, 4, -1], sigma = {} : {:0.6f}\n'
'(for sigma = 2, this value should be about 0.324652'.format(sigma, sim))
- 编写高斯核函数gaussianKernel.py
def gaussian_kernel(x1, x2, sigma):
x1 = x1.flatten() #z转换为1维数组
x2 = x2.flatten()
sim = 0
sim=np.exp(((x1-x2).dot(x1-x2))/(-2*sigma*sigma))
return sim
与期望值进行比较,验证程序正确性:
- 可视化训练集2
'''第4部分 可视化数据集2'''
print('Loading and Visualizing Data ...')
data = scio.loadmat('ex6data2.mat')#加载矩阵格式的数据集2
X = data['X'] #提取原始输入特征矩阵 2个原始特征
y = data['y'].flatten() #提取标签 转换为1维数组
m = y.size #样本数
#可视化训练集2
pd.plot_data(X, y)
- 可视化效果
很显然训练集2是线性不可分的,而且原始输入特征维度n=2,比较小,训练样本数m比较多,此时可以考虑使用带高斯核函数的SVM。
- 使用带有高斯核函数的SVM进行训练
'''第5部分 对训练集2使用带有高斯核函数的SVM进行训练'''
print('Training SVM with RFB(Gaussian) Kernel (this may take 1 to 2 minutes) ...')
#参数设置
c = 1
sigma = 0.1
#调用自己写的高斯核函数 返回新的特征向量矩阵
def gaussian_kernel(x_1, x_2):
n1 = x_1.shape[0]
n2 = x_2.shape[0]
result = np.zeros((n1, n2))
for i in range(n1):
for j in range(n2):
result[i, j] = gk.gaussian_kernel(x_1[i], x_2[j], sigma)
return result
# clf = svm.SVC(c, kernel=gaussian_kernel) #使用自己手写的高斯核函数
clf = svm.SVC(c, kernel='rbf', gamma=np.power(sigma, -2)) #使用封装好的高斯核函数 rbf
clf.fit(X, y) #进行训练
print('Training complete!')
pd.plot_data(X, y) #可视化训练集
vb.visualize_boundary(clf, X, 0, 1, .4, 1.0) #可视化决策边界
注意当原始特征取值范围差异很大时,要先进行特征缩放,再使用核函数生成新的特征矩阵,再用SVM训练。不过上例中不需要特征缩放。
- 可视化分类效果
- 可视化训练集3
'''第6部分 可视化训练集3'''
print('Loading and Visualizing Data ...')
data = scio.loadmat('ex6data3.mat')#加载矩阵格式的数据集2
X = data['X']#提取原始输入特征矩阵 2个原始特征
y = data['y'].flatten()#提取标签 转换为1维数组
m = y.size #样本数
# 可视化训练集
pd.plot_data(X, y)
- 训练集3可视化效果
训练集3存在一些异常点。
- 使用带有高斯核函数的SVM进行训练
'''第7部分 对训练集3使用带有高斯核函数的SVM进行训练'''
clf = svm.SVC(c, kernel='rbf', gamma=np.power(sigma, -2)) #使用封装好的高斯核函数 rbf
clf.fit(X, y)#进行训练
pd.plot_data(X, y) #可视化训练集
vb.visualize_boundary(clf, X, -.5, .3, -.8, .6) #可视化决策边界
- 可视化分类效果
3.实验1 SVM简单使用 完整项目代码
下载链接 下载密码:d8ti
4.垃圾邮件分类
- 打开主程序ex6_spam.py
'''第1部分 邮件预处理'''
#提取邮件中的单词 并得到这些单词在词汇表中的序号 存储在数组中
print('Preprocessing sample email (emailSample1.txt) ...')
file_contents = open('emailSample1.txt', 'r').read() #读入示例邮件内容
word_indices = pe.process_email(file_contents) #对邮件内容进行预处理
print('Word Indices: ')
print(word_indices)
- 编写邮件处理程序processEmail.py
def process_email(email_contents):
vocab_list = get_vocab_list() #得到词汇字典
word_indices = np.array([], dtype=np.int64) #存储邮件中的单词在词汇标中的序号
word_list=[]
#邮件预处理
email_contents = email_contents.lower() #将邮件内容转换为小写
#去除所有html标签
email_contents = re.sub('<[^<>]+>', ' ', email_contents)
# 把邮件内容中任何数字替换为number
email_contents = re.sub('[0-9]+', 'number', email_contents)
# 把邮件中任何以http或https开头的url 替换为httpadddr
email_contents = re.sub('(http|https)://[^\s]*', 'httpaddr', email_contents)
# 把邮件中任何邮箱地址替换为 emailaddr
email_contents = re.sub('[^\s]+@[^\s]+', 'emailaddr', email_contents)
# 把邮件中的$符号 替换为dollar
email_contents = re.sub('[$]+', 'dollar', email_contents)
# 对邮件分词
print('==== Processed Email ====')
stemmer = nltk.stem.porter.PorterStemmer()
# print('email contents : {}'.format(email_contents))
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
if token in vocab_list.values():
word_list.append(list(vocab_list.keys())[list(vocab_list.values()).index(token)])
print(token)
print('==================')
word_indices=np.array(word_list,dtype=np.int64)
return word_indices
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
- 邮件中的单词在词汇表中的序号
- 提取邮件特征
'''第2部分 邮件特征提取'''
#将邮件内容转换为向量 基于之前提取的邮件单词在词汇表中的序号
print('Extracting Features from sample email (emailSample1.txt) ... ')
features = ef.email_features(word_indices)
print('Length of feature vector: {}'.format(features.size)) #特征向量大小
print('Number of non-zero entries: {}'.format(np.flatnonzero(features).size)) #向量中非0项的数量
- 编写特征提取程序emailFeatures.py
def email_features(word_indices):
n = 1899 #词汇表中词的数量 特征向量大小
features = np.zeros(n + 1) #n+1 单词序号从1开始 不+1的话 出现序号为1899的单词 就会越界
features[word_indices]=1
features=features[1:]
return features
- 训练线性SVM进行垃圾邮件分类
'''第3部分 训练线性SVM进行垃圾邮件分类'''
data = scio.loadmat('spamTrain.mat') #加载矩阵格式的邮件数据集
X = data['X'] #输入特征矩阵
y = data['y'].flatten() #标签 并转换为一维数组 0/1
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) #训练svm
p = clf.predict(X) #在训练集上进行预测
print('Training Accuracy: {}'.format(np.mean(p == y) * 100)) #训练集上的准确率
- 计算线性svm分类器在测试集上的准确率
'''第4部分 在测试集上测试线性svm分类器的准确率'''
# 加载测试集
data = scio.loadmat('spamTest.mat')
Xtest = data['Xtest']
ytest = data['ytest'].flatten()
print('Evaluating the trained linear SVM on a test set ...')
p = clf.predict(Xtest) #在测试集上的预测结果
print('Test Accuracy: {}'.format(np.mean(p == ytest) * 100)) #测试集上的准确率
- 返回权重最大的前15个单词及其权重
'''第5部分 通过训练好的分类器 打印权重最高的前15个词 邮件中出现这些词更容易是垃圾邮件'''
vocab_list = pe.get_vocab_list() #得到词汇表 存在字典中
indices = np.argsort(clf.coef_).flatten()[::-1] #对权重序号进行从大到小排序 并返回
print(indices)
for i in range(15): #打印权重最大的前15个词 及其对应的权重
print('{} ({:0.6f})'.format(vocab_list[indices[i]], clf.coef_.flatten()[indices[i]]))
5.垃圾邮件分类完整项目代码
下载链接 下载密码:q5zc