k-近邻算法
算法流程
1.收集数据
2.计算目标数据与样本数据的距离
3.将计算得到的距离进行递增排序
4.选取与目标数据距离最小的k个样本数据
5.统计前k个样本数据分类出现的频率
5.前k个样本数据值对应的分类出现频率最高的类别则为目标数据的类别
距离计算方法
使用欧式距离公式,计算两个向量点xA和xB之间的距离:
代码实现
from numpy import *
import operator # 运算符模块
# 导入数据
def createDataSet():
group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
#k-近邻算法
def classify(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # 样本数据集样本数目
diffMat = tile(inX, (dataSetSize, 1)) - dataSet # 将目标数据与每个样本数据做差
sqDiffMat = diffMat ** 2 # 将矩阵每个元素平方
sqDistances = sqDiffMat.sum(axis=1) # 将矩阵中每行元素相加
distances = sqDistances ** 0.5 # 将矩阵中每个元素开方得到距离
# 将距离递增排序
sortedDistIndicies = distances.argsort() # 返回排序后索引
# 统计前k个样本数据的分类出现频率
classCount = {} # 创建空字典
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] # 根据距离递增依次获取样本对应的标签
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 对应标签计数加一
# 对前k个样本数据分类按照出现频率排序 reverse=True降序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
#测试数据
group, labels = createDataSet()
print(classify([0, 0], group, labels, 3))
示例
示例1: 在约会网站上使用k-近邻算法
1.准备数据:从文本文件中解析数据
# 将文本记录转换成NumPy
def file2matrix(filename):
fr = open(filename) # 打开文本文件
arrayOLines = fr.readlines() # 按行读取整个文件,返回list
numberOfLines = len(arrayOLines) # 每行内容长度
returnMat = zeros((numberOfLines, 3)) # 创建一个与文本文件中数据行数对应,列数为3的0矩阵
classLabelVector = [] # 创建类标签列表
index = 0
# 解析文件数据到列表
for line in arrayOLines:
line = line.strip() # 去掉文本中的空格
listFromLine = line.split('\t') # 使用tab将整行的数据分割成元素列表
returnMat[index, :] = listFromLine[0:3] # 将分割出来的元素赋值给创建的0矩阵
# 把该样本对应的标签放至标签集,顺序与样本集对应
labels = {'didntLike': 1, 'smallDoses': 2, 'largeDoses': 3}
# python语言中可以使用-1表示列表中的最后一列元素
classLabelVector.append(labels[listFromLine[-1]])
index += 1
return returnMat, classLabelVector
2.分析数据:使用Matplotlib创建散点图
# 使用Matplotlib创建散点图
fig = plt.figure() # 新建一个画布
ax = fig.add_subplot(111) # 1*1网格,第一子图
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0 * array(datingLabels),
15.0 * array(datingLabels)) # 散点图---》绘制矩阵的第二、三列
#scatter(x,y,s=1,c="g",marker="s",linewidths=0)
#s:散列点的大小,c:散列点的颜色,marker:形状,linewidths:边框宽度
plt.show()
3.准备数据:归一化数值
newValue = (oldValue-min)/(max-min) 其中min和max分别是数据集中的最小特征值和最大特征值
# 归一化特征值
def autoNorm(dataSet):
minVals = dataSet.min(0)
# min(0)返回该矩阵中每一列的最小值
# min(1)返回该矩阵中每一行的最小值
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1)) # 数据集中的数据与minVals做差
normDataSet = normDataSet / tile(ranges, (m, 1)) #(oldValue-min)/(max-min)
return normDataSet, ranges, minVals
4.测试算法:作为完整程序验证分类器
用已有数据的90%作为训练样本来训练分类器,使用其余10%的数据去测试分类器
在1000条数据中拿出100条数据来验证分类器
# 分类器针对约会网站的测试代码
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('datingTestSet.txt') # 从文档解析数据
normDataSet, ranges, minVals = autoNorm(datingDataMat) # 归一化处理数据
m = normDataSet.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0 # 错误计数器
for i in range(numTestVecs):
classifierResult = classify(normDataSet[i, :], normDataSet[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]): errorCount += 1.0
print("the total error rate is:%f" % (errorCount / float(numTestVecs)))
5.使用算法:构建完整可用系统
输入一组数据利用分类器预测数据结果
#约会网站预测函数
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('datingTestSet1.txt') # 从文本文档解析数据
normMat,ranges,minVals = autoNorm(datingDataMat) # 将数据归一化处理
inArr = array([ffMiles,percentTats,iceCream])
classifierResult = classify((inArr-minVals)/ranges,normMat,datingLabels,3) #将目标数据归一化处理后进行分类
print("You will probably like this person:",resultList[classifierResult-1]) # 输出对目标数据预测的结果
示例2:手写识别系统
为了简单起见,这里构造的系统只能识别数字0到9 ,尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便理解,我们还是将图像转换为文本格式。
1.准备数据:将图像转换为测试向量
将图像格式处理为一个向量。图像为3232的二进制图形矩阵,将其转化为11024的向量。
# 将图像转化为向量
def img2vector(filename):
returnVec = zeros((1, 1024)) # 构造一个1*1024的0矩阵
fr = open(filename)
for i in range(32):
lineStr = fr.readline() # 只读取文件的当前行,返回str
for j in range(32):
returnVec[0, 32 * i + j] = int(lineStr[j]) # 将图片中数字赋值到新的向量的对应位置
return returnVec
2.测试算法:使用k-近邻算法识别手写数字
将处理好的向量中的数据输入到分类器中,测试分类器的执行效果。
# 手写数字识别系统的测试代码
def handwritingClassTest():
hwLabels = []
trainingFileList = os.listdir('trainingDigits') # 返回path指定的目录中的条目的名称(任意顺序)
m = len(trainingFileList) # 统计文件夹中的数据条目数量
trainingMat = zeros((m, 1024)) # 创建一个m行1024列的零矩阵
for i in range(m):
fileNameStr = trainingFileList[i] # 获取对应位置文件名
fileStr = fileNameStr.split('.')[0] # 用.分割文件名去掉文件后缀
classNumberStr = int(fileStr.split('_')[0]) # 获取图片文件对应的数字
hwLabels.append(classNumberStr) # 将对应数字加入到标签列表中
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr) # 将文件夹列表中的文件一次转换成向量存入向量集trainingMat中
testFileList = os.listdir('testDigits')
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumberStr = int(fileStr.split('_')[0])
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
classifierResult = classify(vectorUnderTest, trainingMat, hwLabels, 3)
print("the classifier came back with:%d,the real answer is:%d" % (classifierResult, classNumberStr))
if (classifierResult != classNumberStr): errorCount += 1.0
print("\nthe total number of errors is:%d" % errorCount)
print("\nthe total error rate is:%f" % (errorCount / float(mTest)))
小结
- k-近邻算法是分类数据最简单最有效的算法。
- k-近邻算法必须保存全部的数据集,训练数据集过大,会使用大量的存储空间。
- k-近邻算法必须对数据集中的每个数据计算距离值,会非常耗时。
- k-近邻算法无法给出任何数据的基础结构信息,无法知晓平均实例样本和典型实例样本有什么特征。