原理:K-近邻算法通过计算不同样本之间的距离来分类物品。
使用前,我们需要有一个训练样本集,并且样本集中每个数据都存在标签,即我们知到样本集中每一数据与其所述分类的对应关系。输入没有标签的新数据,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似(最近邻)的分类标签。一般来说,我们只选择样本数据集中前K个最相似的数据,这就是k-近邻算法中k的由来,通常k是不大于20的整数。最后,选择k个最相似数据中出现频率最高的分类,作为新数据的分类。
K-近邻算法
k-近邻算法的一般流程
1、收集数据,可以使用任何办法
2、准本数据,距离计算所需要的数值,最好是结构比的数据格式
3、分析数据,可使用任何办法
4、测试算法,计算错误率
5、使用算法,首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
值得注意的是,由于算法本身的特殊性,k-近邻算法无须训练,即可直接参与分类,其计算量主要集中在计算不同样本的特征距离。
为便于测试及学习,博主将所用到的数据集放到了百度网盘上分享
链接:https://pan.baidu.com/s/1XUDa38MRpf4GjV-IpuhSIg
提取码:01gm
对未知类别属性的数据集中每个点依次执行以下操作:
(1)计算已知类别数据集中的每个点与当前点之间的距离
(2)按照距离递增排序
(3)选取与当前点距离最小的k个点
(4)确定前k个点所在类别的出现频率
(5)返回前k个点出现频率最高的类别作为当前点的预测分类
下面给出一个简单的测试样例,其中每个样本包含两个特征
import numpy as np
import operator
def kNN_classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
# 计算欧式距离
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort()
classCount = {}
# 选择距离最小的k个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def testDataSet():
data = np.array(
[[0.1, 0.1], [0.31, 0.15], [0.66, 0.93], [0.54, 0.89], [1.0, 0.3], [0.86, 0.21], [0.47, -0.55], [0.58, -0.31]])
labels = ['M', 'M', 'N', 'N', 'P', 'P', 'Q', 'Q']
return data, labels
data, labels = testDataSet()
predict = kNN_classify0([0.1, 0.5], data, labels, 4)
print("kNN predicts the [0.1,0.5] belongs to ", predict)
"""
运行结果:
kNN predict the [0.1,0.5] belongs to M
"""
问题描述:
海伦一直使用某在线约会网站寻找适合自己的约会对象,尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:
我们了解到,海伦对于以下几种约会对象的特征比较关注
下面直接给出完整代码,注释已经较为明确:
import numpy as np
import operator
import time
def kNN_classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
# 计算欧式距离
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort()
classCount = {}
# 选择距离最小的k个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def file2matrix(filename):
"""
从文本数据读取海伦关于三个特征的数据集
:param filename:读取以存储文本存储的海伦个人爱好数据
:return: 读取得到的数据及标签
"""
love_dictionary = {'largeDoses': 3, 'smallDoses': 2, 'didntLike': 1}
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 获取文件的行数
# 为数据存储预留空间
returnMat = np.zeros((numberOfLines, 3))
classLabelVector = []
index = 0
# 读取数据
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
if (listFromLine[-1].isdigit()):
classLabelVector.append(int(listFromLine[-1]))
else:
classLabelVector.append(love_dictionary.get(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
def autoNorm(dataSet):
"""
归一化输入的数据集,由于算法本身的特性,输入数据的极端值对计算结果影响很大,另外在本例中,由于三个数据的重要性是相同的,
因此不同的数据不应该因为其取值范围的跨度导致对分类的影响,此种情况下,我们经常采用归一化的手段,将不同取值范围的数据归一化到 [0,1] 区间
:param dataSet:输入获取到的数据集
:return: 返回归一化结果
"""
# 获取归一范围
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
def datingClassTest():
"""
约会网站的改进算法,利用kNN完成海伦的需求,同时验证算法的准确性以及错误预测数
kNN的训练和测试过程是融为一体的
:return: none
"""
valRate = 0.10 # 选择10%的数据作为验证集,测试其准确率
datingData, datingLabels = file2matrix('datingTestSet2.txt') # 利用前面的函数加载数据
"""
打乱数据集,这样可以更客观地评价算法地准确性,利用时间作为随机数的种子,
np.random.seed(timeseed)用于设置随机数地种子,确保对于datingDataMat,datingLabels的打乱是对应的
"""
timeseed = int(time.time())
np.random.seed(timeseed)
np.random.shuffle(datingData)
np.random.seed(timeseed)
np.random.shuffle(datingLabels)
normMat, ranges, minVals = autoNorm(datingData) # 归一化数据
m = normMat.shape[0]
numTestVecs = int(m * valRate)
errorNum = 0.0
for i in range(numTestVecs):
classifierResult = kNN_classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorNum += 1.0
print("the total accuracy is: %f" % (1 - errorNum / float(numTestVecs)))
print("error classification's numbers: ", int(errorNum))
datingClassTest()
"""
部分运行结果:
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
.......................................................
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the total accuracy is: 0.910000
error classification's numbers: 9
"""
我们已经利用了上述的分类器做了一个小小的测试,接下来,我们就可以输入一段个人信息,然后利用程序预测海伦对他的喜欢程度预测值了。
代码如下:
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
percentTats = float(input(
"percentage of time spent playing video games?"))
ffMiles = float(input("frequent flier miles earned per year?"))
iceCream = float(input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = np.array([ffMiles, percentTats, iceCream, ])
classifierResult = kNN_classify0((inArr -
minVals) / ranges, normMat, datingLabels, 3)
print("You will probably like this person: %s" % resultList[classifierResult - 1])
classifyPerson()
"""
运行结果:
percentage of time spent playing video games?>? 0.1
frequent flier miles earned per year?>? 3000
liters of ice cream consumed per year?>? 2.2
You will probably like this person: in small doses
"""
0-9手写数字识别是一个非常基础的项目,机器学习可以通过多种方法实现对其的分类,由于其单个数据量较少,我们可以利用支持向量机对其多分类处理,也可以利用简单的全连接神经网络实现分类。下面,我们将利用kNN实现对0-9数据的分类,实现手写数字的识别。
在前述的百度网盘链接中,已经准备了以文本文档格式存储的手写数字,其中每个手写数字的尺寸为32*32的二值图。以文本格式将有助于我们利用kNN实现数字的识别分类。
我们将按照下述流程实现手写数字的识别与分类:
(1)收集数据:利用网盘链接中的文本文件
(2)准备数据:编写函数img2vector(),将图像格式转换为分类器使用的list格式
(3)分析数据:我们可以文本文档打开数据检查是否符合要求
(4)测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的区别在于测试样本是已经包含标签的数据,即已分类。
(5)使用算法识别数字
前面例子使用的分类器,我们需要将图像格式化处理为一个向量,输入分类器更方便。因此我们将把3232的二值化图像化为11024的向量。
import os
def img2vector(filename):
"""
格式化输入的数据图像,转换为向量
:param filename: 文本文件名称
:return: 1*1024的数字向量
"""
dataVect = np.zeros((1, 1024)) #预先分配一部分数据
fr = open(filename)
for i in range(32):
lineText = fr.readline()
for j in range(32):
dataVect[0, 32 * i + j] = int(lineText[j]) # 利用偏移量使整个手写数字数据分布在数组上
return dataVect
def handwritingClassficationTest():
hwLabels = []
trainingFileList = os.listdir('trainingDigits') # 加载训练数据
m = len(trainingFileList)
trainingMat = np.zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] # 切分数据
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = os.listdir('testDigits') # 遍历测试数据集
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0] # 切分数据
classNumStr = int(fileStr.split('_')[0])
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
classifierResult = kNN_classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
if (classifierResult != classNumStr): errorCount += 1.0
# 打印测试数据
print("\nthe total number of errors is: %d" % errorCount)
print("\nthe total accuracy is: %f" % (1 - errorCount / float(mTest)))
handwritingClassficationTest()
"""
部分训练得到的结果:
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the total number of errors is: 10
the total accuracy is: 0.989429
"""
在该数据集上,我们得到的正确率为98.9429%,改变k值,修改函数随机选取训练样本,改变训练样本的数目,都会改变kNN的准确率。
值得注意的是,该算法效率不高,且对CPU利用率不高,另一方面,如果可以利用GPU并行加速计算距离,将大大提高运算速度和效率。
k决策树是k-近邻算法优化版,也可以节省大量的计算开销。
k近邻算法是分类数据最简单最有效的算法,我们通过约会网站改进和手写数字识别,检验了kNN的可行性及简便性。k近邻算法是基于实例的学习,使用算法时我们必须有接近实际数据的训练样本数据。
k近邻算法必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间。同时我们需要对数据集中的每个数据相互之间计算距离值,这样的算法也属于计算密集型算法,实际使用需要一定的时间。同时,kNN和其他的算法略有不同,它没有显著式的训练过程,其训练过程体现在预测阶段。
kNN还有一个缺陷,它无法给出任何数据的基础结构信息,因此我们也难以知晓平均实例样本和典型实例样本具有何种特征。这一方面,基于iD3算法的决策树优于kNN,其利用香农熵,准确且量化的反映出了各类数据的混乱程度。
后续博主还会更新k决策树,解决上述的部分缺陷,使之兼具简洁易懂与运算快捷。机器学习算法系列博文还会更新其他的机器学习算法,学习永无止境。