文章目录
- 8. sklearn机器学习实战
- 8.1 朴素贝叶斯算法的中文邮件分类
- 8.1.1 数据集
- 8.1.1.1 训练集
- 8.1.1.2 测试集
- 8.1.2 提取邮件文本有效词汇
- 8.1.3 获取全部邮件文本中出现次数最多的前N个词汇
- 8.1.4 使用朴素贝叶斯模型进行邮件分类
- 8.2 支持向量机手写数字图片识别
- 8.2.1 数据集
- 8.2.2 图像数据读取
- 8.2.3 交叉验证与网格搜索
- 8.2.3.1 交叉验证
- 8.2.3.1.1 K 折叠
- 8.2.3.1.2 随机拆分
- 8.2.3.1.3 逐个测试
- 8.2.3.2 网格搜索
- 8.3 KNN 算法基本原理与 sklearn 实现
- 8.3.1 关于 KNN 算法
- 8.3.2 基本思路
- 8.3.3 基本步骤
- 8.3.4 sklearn 实现
- 8.4 分层聚类算法原理与应用
- 8.4.1 基本思路
- 8.4.2 缺点
- 8.4.3 sklearn 实现
- 8.5 DBSCAN 算法原理与应用
- 8.5.1 DBSCAN 算法原理
- 8.5.1.1 简介
- 8.5.1.2 基本概念
- 8.5.1.3 工作过程
- 8.5.2 DBSCAN 算法应用
8. sklearn机器学习实战
8.1 朴素贝叶斯算法的中文邮件分类
实验用到的类库说明
# re.sub 用于替换邮件文本中的干扰符号
# collections.Counter 统计邮件文本中词语的出现次数
# itertools.chain 统计邮件文本中词语的出现次数
# numpy.array 机器学习中数据输入模型时一般使用数组
# jieba.cut 对邮件文件进行分词时使用
# sklearn.naive_bayes.MultinomialNB 使用朴素贝叶斯算法创建模型时使用
from re import sub
from collections import Counter
from itertools import chain
from numpy import array
from jieba import cut
from sklearn.naive_bayes import MultinomialNB
8.1.1 数据集
8.1.1.1 训练集
0-126 垃圾邮件
127-150 正常邮件
8.1.1.2 测试集
151-155 测试邮件
8.1.2 提取邮件文本有效词汇
目标如下:
提取有效词汇代码
def getWordsFromFile(txtFile):
# 获取每一封邮件中的所有词语
# txtFile:给定的记事本文件,正常邮件或垃圾邮件
words = []
# 所有存储邮件文本内容的记事本文件都使用UTF8编码
with open(txtFile, encoding='utf8') as fp:
for line in fp:
# 遍历每一行,删除两端的空白字符(空格、制表符、换行符)
line = line.strip()
# 过滤干扰字符或无效字符
line = sub(r'[.【】0-9、—。,!~\*]', '', line)
# 分词
line = cut(line)
# 过滤长度为1的词
line = filter(lambda word: len(word) > 1, line)
# 把本行文本预处理得到的词语添加到words列表中
# line 经过 filter 处理之后得到的 filter 对象
# extend 将作为参数的可迭代对象的所有元素添加到调用方的尾部
words.extend(line)
# 返回包含当前邮件文本中所有有效词语的列表
return words
8.1.3 获取全部邮件文本中出现次数最多的前N个词汇
目标如下:
# 存放所有文件中的单词
# 每个元素是一个子列表,每个子列表中存放一个文件中的有效词汇
allWords = []
def getTopNWords(topN):
# 按文件编号顺序处理当前文件夹中所有记事本文件
# 训练集中共151封邮件内容,0.txt到126.txt是垃圾邮件内容
# 127.txt到150.txt为正常邮件内容
txtFiles = [str(i)+'.txt' for i in range(151)]
# 获取训练集中所有邮件中的全部单词
for txtFile in txtFiles:
allWords.append(getWordsFromFile(txtFile))
# 获取并返回出现次数最多的前topN个单词
# *allWords 在传递形参时,进行序列解包,如:*[[1, 2, 3], [4, 5, 6]] -> [1, 2, 3], [4, 5, 6]
# chain(*allWords) chain 可以接收多个可迭代对象,返回 chain 对象。
# 由于 chain 类实现了 __next__() 方法,因此可通过内置函数 next() 获取元素,依次获取接收的多个可迭代对象的元素
# Counter 用于统计可迭代对象中元素出现的次数
# 返回 Counter 对象,是一种特殊的字典
# Counter 对象支持 most_common(count) 方法,用于返回 count 个出现次数最多的元素
freq = Counter(chain(*allWords))
return [w[0] for w in freq.most_common(topN)]
# 全部训练集中出现次数最多的前600个单词
topWords = getTopNWords(600)
8.1.4 使用朴素贝叶斯模型进行邮件分类
首先,获取特征向量
# 获取特征向量,前600个单词的每个单词在每个邮件中出现的频率
vectors = []
for words in allWords:
temp = list(map(lambda x: words.count(x), topWords))
vectors.append(temp)
vectors = array(vectors)
然后,为每个邮件贴上标签,朴素贝叶斯为有监督学习,标签必须已知
# 训练集中每个邮件的标签,1表示垃圾邮件,0表示正常邮件
labels = array([1]*127 + [0]*24)
创建模型,根据训练集进行训练
# 创建模型,使用已知训练集进行训练
model = MultinomialNB()
model.fit(vectors, labels)
编写预测分类方法
def predict(txtFile):
# 获取指定邮件文件内容,返回分类结果
# 首先,提取测试文本的有效词汇
# 然后,获取测试文本的特征向量
words = getWordsFromFile(txtFile)
currentVector = array(tuple(map(lambda x: words.count(x),
topWords)))
# currentVector.reshape(1, -1) 将测试特征向量转化为一行的二位数组
# predict 方法对多个样本进行分类,如果只有一个测试样本,也要放到二维数组中
result = model.predict(currentVector.reshape(1, -1))[0]
# model.predict_proba 返回预测为不同分类的概率
print(model.predict_proba(currentVector.reshape(1, -1)))
return '垃圾邮件' if result==1 else '正常邮件'
对测试集进行测试
# 151.txt至155.txt为测试邮件内容
for mail in ('%d.txt'%i for i in range(151, 156)):
print(mail, predict(mail), sep=':')
8.2 支持向量机手写数字图片识别
实验用到的类库说明
# time.time 记录当前的时间(1970.01.01 00:00:00 - 现在)
# 两次调用这个函数,结果就是运行中间这段代码所需的时间
# os.listdir 列出指定路径下所有的记事本文件,读取数据集时使用
# os.path.basename 从文件路径中取出文件名(如果有后缀,包含后缀)
# PIL.Image 读取图像
# sklearn.svm 支持向量机
# sklearn.model_selection.cross_val_score 交叉验证
# sklearn.model_selection.ShuffleSplit 随机拆分
# sklearn.model_selection.LeaveOneOut 逐个测试
# sklearn.model_selection.GridSearchCV 网格搜索
from time import time
from os import listdir
from os.path import basename
from PIL import Image
from sklearn import svm
from sklearn.model_selection import cross_val_score,\
ShuffleSplit, LeaveOneOut
from sklearn.model_selection import GridSearchCV
8.2.1 数据集
使用画图程序创建的宽度 * 长度为 30 * 60 的图片
部分数据集展示
部分干扰因素可以在采集数据时通过二值化去掉
另外,还有一个 digits.txt 文件,记录了数据集每一张图片中的真实数字,在这只显示部分内容
8.2.2 图像数据读取
加载数据代码
# 图像尺寸
width, height = 30, 60
def loadDigits(dstDir='datasets'):
# 获取所有图像文件名
digitsFile = [dstDir+'\\'+fn for fn in listdir(dstDir)
if fn.endswith('.jpg')]
# 按编号排序
# 由于 digits.txt 文件中的内容是按照数据集顺序排序的,所以图片顺序也要与之对应
digitsFile.sort(key=lambda fn: int(basename(fn)[:-4]))
# digitsData 用于存放读取的图片中数字信息
# 每个图片中所有像素值存放于digitsData中的一个子列表
digitsData = []
for fn in digitsFile:
with Image.open(fn) as im:
# im.getpixel 返回指定行、列的像素值((R, G, B))
data = [sum(im.getpixel((w,h)))/len(im.getpixel((w,h)))
for w in range(width)
for h in range(height)]
digitsData.append(data)
# digitsLabel用于存放图片中数字的标准分类
with open(dstDir+'\\digits.txt') as fp:
digitsLabel = fp.readlines()
# label.strip() 删掉每一行尾部的换行符
digitsLabel = [label.strip() for label in digitsLabel]
return (digitsData, digitsLabel)
# 加载数据
data = loadDigits()
print('数据加载完成。')
8.2.3 交叉验证与网格搜索
8.2.3.1 交叉验证
创建模型
# 创建模型
# svm.SVC 基于支持向量机的分类算法
svcClassifier = svm.SVC(kernel="linear", C=1000, gamma=0.001)
8.2.3.1.1 K 折叠
# 交叉验证 通过交叉验证来知道模型的参数是否合适
start = time()
# cross_val_score(待评估模型, 数据, 标签, cv=8)
# cv = 8 如果 cv 是整数,表示 K 折叠,8 就是 8折叠
# 8 折叠 将传入的数据集和标签分为 8 份,每次选取一份作为测试集,其他七份作为训练集,最后得到 8 个评分
scores = cross_val_score(svcClassifier, data[0], data[1], cv=8)
print('交叉验证(k折叠)得分情况:\n', scores)
# scores.mean() 将 8 个评分的平均值作为模型最终的得分
print('平均分:\n', scores.mean())
print('用时(秒):', time()-start)
8.2.3.1.2 随机拆分
start = time()
# cross_val_score(待评估模型, 数据, 标签, cv=ShuffleSplit(test_size=0.3,
# train_size=0.7,
# n_splits=10))
# 此时使用的不再是 K 折叠,而是随机拆分
# 每次从数据集中取 30% 作测试集,70% 作训练集,重复 10 次,最后得到 10 个评分
scores = cross_val_score(svcClassifier, data[0], data[1],
cv=ShuffleSplit(test_size=0.3,
train_size=0.7,
n_splits=10))
print('交叉验证(随机拆分)得分情况:\n', scores)
# scores.mean() 将 10 个评分的平均值作为模型最终的得分
print('平均分:\n', scores.mean())
print('用时(秒):', time()-start)
8.2.3.1.3 逐个测试
start = time()
# cross_val_score(待评估模型, 数据, 标签, cv=LeaveOneOut())
# 此时使用的是逐个测试
# 每次将数据集的一个数据作为测试集,其他作为训练集,共得到与数据集数目相等个数的评分,此例中,为 1617 个
scores = cross_val_score(svcClassifier, data[0], data[1],
cv=LeaveOneOut())
print('交叉验证(逐个测试)得分情况:\n', scores)
# scores.mean() 将 1617 个评分的平均值作为模型最终的得分
print('平均分:\n', scores.mean())
print('用时(秒):', time()-start)
8.2.3.2 网格搜索
创建模型
# 创建模型,使用默认参数
svcClassifier = svm.SVC()
设置待测试参数
# 待测试的参数
parameters = {'kernel': ('linear',),
'C': (0.001, 1, 10, 100),
'gamma':(0.001, 0.1, 10)}
网格搜索
start = time()
# GridSearchCV 在给定的参数范围内寻找最优的参数,测试过程中自动进行交叉验证
clf = GridSearchCV(svcClassifier, parameters)
clf.fit(data[0], data[1])
# 解除注释可以查看详细结果
# print(clf.cv_results_)
print(clf.best_params_)
print('得分:', clf.score(data[0], data[1]))
print('用时(秒):', time()-start)
8.3 KNN 算法基本原理与 sklearn 实现
8.3.1 关于 KNN 算法
KNN 算法的简称是 K-Nearest Neighbor,叫做近邻算法,有监督学习,既可以用于分类,也可以用于回归,在这,只讨论分类
8.3.2 基本思路
在样本空间内查找 K 个最相似或者距离最近的样本,然后根据 K 个最相似的样本对未知样本进行分类
8.3.3 基本步骤
- 对数据进行预处理,提取特征向量,对原始数据进行重新表达
能否确定每一个样本在二维平面上的坐标
- 确定距离计算公式,并计算已知样本空间中所有样本与未知样本的距离
确定距离计算公式(欧几里得的直线距离/曼哈顿的城市距离),在这里使用欧式距离 - 对所有距离按升序排序
- 确定并选取与未知样本距离最小的 K 个样本
K 的参数值对分类结果有影响 - 统计选取的 K 个样本中每个样本所属类别的出现频率
- 把出现频率最高的类别作为预测结果,认为未知样本属于这个类别
注:这种算法要求样本空间内样本所属分类比较均衡,即所属的每一个类别中样本的数量不能相差太大
8.3.4 sklearn 实现
实验用到的类库说明
# sklearn.neighbors.KNeighborsClassifier KNN
from sklearn.neighbors import KNeighborsClassifier
模拟数据集
# X 模拟数据集
X = [[1, 5], [2, 4], [2.2, 5],
[4, 1.5], [5, 1], [5, 2], [5, 3], [6, 2],
[7.5, 4.5], [8.5, 4], [7.9, 5.1], [8.2, 5]]
标签
# y 每一个点所属的类别
y = [0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2]
创建模型,训练后进行预测
# 创建模型,K = 3
knn = KNeighborsClassifier(n_neighbors=3)
# 训练模型
knn.fit(X, y)
# [4.8, 5.1] 测试点,进行预测
print('K=3 时的预测结果', knn.predict([[4.8, 5.1]]))
改变 K 值,重新预测
# 改变 K 值为 9
knn = KNeighborsClassifier(n_neighbors=9)
knn.fit(X, y)
print('K=9 时的预测结果', knn.predict([[4.8, 5.1]]))
查看属于不同分类的概率
# predict_proba 预测测试点属于不同分类的概率
print('K=9 时测试点属于不同分类的概率', knn.predict_proba([[4.8, 5.1]]))
8.4 分层聚类算法原理与应用
分层聚类又称系统聚类
或系谱聚类
8.4.1 基本思路
首先把所有样本看作各自一类(如果有 X 个样本,初始状态就有 X 类),定义类间距离计算公式(欧式距离、曼哈顿距离或其他距离),选择距离最小的一对元素合并成一个新的类,重新计算各类之间的距离并重复上述步骤,直至将所有原始元素划分为指定数量的类(每执行一次,减少一个类)
8.4.2 缺点
该算法的计算复杂度非常高,不适合大数据聚类问题
8.4.3 sklearn 实现
实验用到的类库说明
# matplotlib.pyplot 可视化
# sklearn.datasets.make_blobs 生成测试数据
# sklearn.cluster.AgglomerativeClustering 系统聚类
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import AgglomerativeClustering
定义可视化需要用到的散点颜色和符号
# 定义绘制散点图时使用的颜色和散点符号
# 依次使用不同的颜色和符号绘制每个类的散点图
colors = 'rgby'
markers = 'o*v+'
创建聚类和可视化函数
# n_clusters 将作为系统聚类算法的参数,指定最后需要聚成几类
def AgglomerativeTest(n_clusters):
# 指定 n_clusters 的值只能在 1-4 之间,否则抛出异常
assert 1 <= n_clusters <= 4
# 若 linkage='ward',则 affinity 必须为'euclidean','euclidean' 表示使用欧氏距离
# predictResult 一个与 data 长度相同的标签列表,每一个元素代表对应于原本数据(data)中样本的分类标签
predictResult = AgglomerativeClustering(n_clusters=n_clusters,
affinity='euclidean',
linkage='ward').fit_predict(data)
# 聚类之后通过可视化查看结果
for i in range(n_clusters):
# predictResult==i 会得到一个bool列表,长度与 predictResult 相等
# 每次循环会依次将分类为 0,1,2... 的数据样本取出放到 subData 中
# 这样,subData 中的值就是每个分类对应的所有样本
subData = data[predictResult == i]
# c=colors[i], marker=markers[i] 对于不同分类的样本,分别使用不同的颜色和符号表示
# s=40 点的大小
print(subData[:,0])
plt.scatter(subData[:,0], subData[:,1], c=colors[i], marker=markers[i], s=40)
plt.show()
生成随机数据
# 生成随机数据,200个点,分成 3 类,返回样本及标签
data, labels = make_blobs(n_samples=200, centers=3)
实验结果
8.5 DBSCAN 算法原理与应用
8.5.1 DBSCAN 算法原理
全称 Density-Based Spatial Clustering of Applications with Noise
8.5.1.1 简介
DBSCAN 属于密度
聚类算法,把类定义为密度相连对象的最大集合,通过在样本空间中不断搜索高密度的核心样本并进行扩展得到最大集合完成聚类,能够在带有噪点的样本空间中发现任意形状的聚类,同时排除噪点
8.5.1.2 基本概念
- 核心样本
如果给定样本的邻域(最大距离为 eps)内样本数量超过阈值 min_samples,则成为核心样本 - 边界样本
在邻域内样本的数量小于阈值,但是落在核心样本的邻域内的样本 - 噪声样本
既不是核心样本也不是边界样本的样本 - 直接密度可达
如果样本 q 在核心样本 p 的邻域内,则称 p 是可以到达 q 的,换言之,q 从 p 出发是直接密度可达的其他样本从核心样本出发是直接密度可达的
- 密度可达
集合中的样本链 p1、p2、p3、…、pn,如果每个样本 pi + 1 从 pi 出发都是直接密度可达的(pi + 1 在 核心样本 pi 的邻域内),则称 pn 从 p1 出发是密度可达的(pn 在 核心样本 p1 的邻域内) - 密度相连
集合中如果存在样本 o 使得样本 p 和 q 从 o 出发都是密度可达的(p 和 q 都在 核心样本 o 的邻域内),则称样本 p 和 q 是互相密度相连的
8.5.1.3 工作过程
- 定义邻域半径(eps)和样本数量阈值(min_samples)
如果 eps 设置过大,min_samples 设置过小,会导致核心样本数量过多
如果 eps 设置过小,min_samples 设置过大,会导致核心样本数量过少 - 从样本空间中抽取一个尚未访问过的样本 p
- 如果 p 是核心样本,进入步骤 4;否则,根据实际情况将其标记为噪声样本或某个类的边界样本,进入步骤 2
- 找出样本 p 出发的所有密度相连样本,构成一个聚类 Cp(该聚类的边界样本都是非核心样本),并标记这些样本为已访问
- 如果全部样本都已访问,算法结束;否则,返回步骤 2
8.5.2 DBSCAN 算法应用
实验用到的类库说明
# matplotlib.pyplot 可视化
# sklearn.cluster.DBSCAN DBSCAN
# sklearn.datasets.make_blobs 生成测试数据
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_blobs
核心方法
# data 需要进行聚类的数据
# eps 邻域半径
# min_samples 阈值
def DBSCANtest(data, eps=0.6, min_samples=8):
# 聚类
db = DBSCAN(eps=eps, min_samples=min_samples).fit(data)
# clusterLabels 聚类标签(数组,表示每个样本所属聚类)
# uniqueClusterLabels 聚类后的种类标签,标签-1对应的样本表示噪点
clusterLabels = db.labels_
uniqueClusterLabels = set(clusterLabels)
# 标记核心对象对应下标为True
# zeros_like 生成全 0 数组,长度与 db.labels_ 的长度一致
# dtype=bool 对于生成的数组中,为 0 的置为 False
# coreSamplesMask 与标签列表 clusterLabels 长度一致的 bool 列表,元素均为 False
coreSamplesMask = np.zeros_like(db.labels_, dtype=bool)
# db.core_sample_indices_ 核心样本的下标数组
coreSamplesMask[db.core_sample_indices_] = True
# 绘制聚类结果
colors = ['red', 'green', 'blue', 'gray', '#88ff66',
'#ff00ff', '#ffff00', '#8888ff', 'black',]
markers = ['v', '^', 'o', '*', 'h', 'd', 'D', '>', 'x']
for label in uniqueClusterLabels:
# 使用最后一种颜色和符号绘制噪声样本
# clusterIndex 是个 bool 数组,其中 True 表示对应样本为当前类
clusterIndex = (clusterLabels == label)
# coreSamples bool 数组,True 表示当前类的核心样本
coreSamples = data[clusterIndex & coreSamplesMask]
plt.scatter(coreSamples[:, 0], coreSamples[:, 1],
c=colors[label], marker=markers[label], s=100)
# 绘制非核心对象
nonCoreSamples = data[clusterIndex & ~coreSamplesMask]
plt.scatter(nonCoreSamples[:, 0], nonCoreSamples[:, 1],
c=colors[label], marker=markers[label], s=20)
plt.show()
生成测试数据并进行聚类
data, labels = make_blobs(n_samples=300, centers=5)
DBSCANtest(data)
修改参数,重新聚类
DBSCANtest(data, 0.8, 15)