数据下载:https://pan.baidu.com/s/1D4vkduD8PVGyPDmx5FRCnw
目录
一、算法概述
1.1 工作原理
1.2 一般流程
1.3 优点
1.4 缺点
二、示例1-构建第一个分类器
2.1 创建数据集和标签
2.2 构建第一个分类器
三、示例2-应用KNN算法改进约会网站的配对效果
3.1 准备数据:从文本文件中解析数据
3.2 分析数据:使用 Matplotlib 创建散点图
3.3 【改进】准备数据:归一化数值
3.4 测试算法:作为完整程序验证分类器
3.5 使用算法:构建完整可用的系统
四、示例3-手写识别系统
五、完整代码
import numpy as np
import operator
def createDataSet():
group = np.array([[1.0, 1.1],
[1.0, 1.0],
[0, 0],
[0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# labels:标签向量
# k:表示用于选择最近邻居的数目
# 注意标签向量的元素数目和矩阵 dataSet 的行数相同
def 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()
# 选择距离最小的 k 个点
classCount = {}
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):
fr = open(filename)
# 得到文件行数
arrayOfLines = fr.readlines()
numOfLines = len(arrayOfLines)
# 创建返回值的Numpy矩阵
returnMat = np.zeros((numOfLines, 3))
# 解析文件数据到列表
classLabelVector = []
index = 0
for line in arrayOfLines:
# 使用line.strip()截取掉所有的回车字符
line = line.strip()
# 使用tab字符将上一步得到的整行数据分割成一个元素列表
listFromLine = line.split('\t')
# 选取前3个元素,存储到特征矩阵
returnMat[index, :] = listFromLine[0: 3]
# Python 可以使用索引值-1表示列表中的最后一列元素
# 利用这种负索引,极其方便地将列表的最后一列存储到向量classLabelVector中
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
此处应注意:
原书中为“datingDataMat, datingLabels = KNN.file2matrix('datingTestSet.txt')”
但实应为“datingDataMat, datingLabels = KNN.file2matrix('datingTestSet2.txt')”
此错误是因为《datingTestSet.txt》文件中数据格式为字符串类型,《datingTestSet2.txt》文件中数据格式为 int 类型。
参考文章:https://blog.csdn.net/jichun4686/article/details/75700448
鉴于上述文章在该模块的阐述极为清晰明了,此处不再赘述。
在处理不同取值范围的特征值时,通常采用的方法是将数值归一化,如将取值范围处理为 0~1 或 -1~1 之间。
下面公式可以将任意取值范围的特征值转化为 0~1 区间内的值:
newValue = (oldValue - min) / (max - min)
# 约会系统-归一化特征值
def autoNorm(dataSet):
# min(0)中的参数0可以从列中选取最小值,而不是从行中选取最小值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
# 特征值矩阵中1000 * 3个值,minVals和ranges的值都为1 * 3
# 故使用numpy库中tile()函数将变量内容复制成输入矩阵同样大小的矩阵
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
上图调用 autoNorm 函数对 datingDataMat 进行归一化处理,下图查看 datingDataMat 。
def datingClassTest():
hoRatio = 0.10
# 使用file2matrix()和autoNorm()函数从文件中读取数据并将其转换为归一化特征值
datingDataMat, datingLabels = file2matrix('testSet.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
# 计算测试向量的数量
# 此步决定了normMat向量中哪些数据用于测试、哪些数据用于分类器的训练样本
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
# 将这两部分数据输入到原始KNN分类器函数classify0()
classifierResult = 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]):
errorCount += 1.0
# 输出结果
print("the total error rate is :%f" % (errorCount / float(numTestVecs)))
执行结果
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
# python3 中使用 input 替代了 row_input
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 = classify0((inArr-minVals) / ranges, normMat, datingLabels, 3)
print("you will probably like this person: ", resultList[classifierResult - 1])
# 手写识别系统-准备数据(图像格式转换为分类器使用的List格式)
def img2Vector(filename):
returnVect = np.zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
# 手写识别系统-测试代码
def handWritingClassTest():
hwLabels = []
# 获取目录内容
trainingFileList = 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 = 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 = 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 error rate is: %f" % (errorCount / float(mTest)))
注意:此处若涉及路径输入,应注意转义字符。
其次在上述动图中,我们可以很直观地发现,加载数据集需要花费较长的时间、然后函数开始依次测试每个文件,此过程耗费的时间依赖于机器速度。
同时正如书中所说:
实际使用这个算法时,算法的执行效率并不高。因为算法需要为每个测试向量做2000次距离计算,每个距离计算包括了 1024个维度浮点运算,总计要执行900次。此外,我们还需要为测试向量准备2MB的存储空间。
from os import listdir
import numpy as np
import operator
def createDataSet():
group = np.array([[1.0, 1.1],
[1.0, 1.0],
[0, 0],
[0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# labels:标签向量
# k:表示用于选择最近邻居的数目
def 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()
# 选择距离最小的k个点
classCount = {}
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):
fr = open(filename)
# 得到文件行数
arrayOfLines = fr.readlines()
numOfLines = len(arrayOfLines)
# 创建返回值的Numpy矩阵
returnMat = np.zeros((numOfLines, 3))
# 解析文件数据到列表
classLabelVector = []
index = 0
for line in arrayOfLines:
# 使用line.strip()截取掉所有的回车字符
line = line.strip()
# 使用tab字符将上一步得到的整行数据分割成一个元素列表
listFromLine = line.split('\t')
# 选取前3个元素,存储到特征矩阵
returnMat[index, :] = listFromLine[0: 3]
# Python 可以使用索引值-1表示列表中的最后一列元素
# 利用这种负索引,极其方便地将列表的最后一列存储到向量classLabelVector中
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
# 约会系统-归一化特征值
def autoNorm(dataSet):
# min(0)中的参数0可以从列中选取最小值,而不是从行中选取最小值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
# 特征值矩阵中1000 * 3个值,minVals和ranges的值都为1 * 3
# 故使用numpy库中tile()函数将变量内容复制成输入矩阵同样大小的矩阵
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
# 约会系统-计算错误率
def datingClassTest():
hoRatio = 0.10
# 使用file2matrix()和autoNorm()函数从文件中读取数据并将其转换为归一化特征值
datingDataMat, datingLabels = file2matrix('testSet.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
# 计算测试向量的数量
# 此步决定了normMat向量中哪些数据用于测试、哪些数据用于分类器的训练样本
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
# 将这两部分数据输入到原始KNN分类器函数classify0()
classifierResult = 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]):
errorCount += 1.0
# 输出结果
print("the total error rate is :%f" % (errorCount / float(numTestVecs)))
# 约会系统-交互
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
# python3 中使用 input 替代了 row_input
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 = classify0((inArr-minVals) / ranges, normMat, datingLabels, 3)
print("you will probably like this person: ", resultList[classifierResult - 1])
# 手写识别系统-准备数据(图像格式转换为分类器使用的List格式)
def img2Vector(filename):
returnVect = np.zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
# 手写识别系统-测试代码
def handWritingClassTest():
hwLabels = []
# 获取目录内容
trainingFileList = 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 = 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 = 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 error rate is: %f" % (errorCount / float(mTest)))