k-近邻算法
标签: 机器学习实践
1. k-近邻算法概述
KNN的特点:
优点 | 缺点 | 适用范围 |
---|---|---|
精度高、对异常值不敏感、无数据输入假定 | 计算复杂度高、空间复杂度高 | 数值型和标称型(离散型数据,变量结果只在有限目标集合中取值) |
工作原理:
存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
案例1:判断一部电影是爱情片还是动作片
表2-1 每部电影的打斗镜头数、接吻镜头数及电影评估类型 |
---|
电影名称 | 打斗镜头 | 接吻镜头 | 电影类型 |
---|---|---|---|
Califoria Man | 3 | 104 | 爱情片 |
He’s Not Really into Dudes | 2 | 100 | 爱情片 |
Beautiful Woman | 1 | 81 | 爱情片 |
Kevin Longblade | 101 | 10 | 动作片 |
Robo Slayer 3000 | 99 | 5 | 动作片 |
Amped II | 98 | 2 | 动作片 |
? | 18 | 90 | 未知 |
表2-2 已知电影于未知电影的距离 |
---|
电影名称 | 与未知电影的距离 |
---|---|
California Man | 20.5 |
He’s Not Really into Dudes | 18.7 |
Beautiful Woman | 19.2 |
Kevin Longblade | 115.3 |
Robo Slayer 3000 | 117.4 |
Ampel II | 118.9 |
在得到了样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到k个距离最近的电影,假定k=3,则三个最靠近的电影依次是He’s Really into Dudes、Beautiful Woman和California Man。k-近邻算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此可以判断未知电影是爱情片
K-近邻算法的一般流程
1. 收集数据:可以使用任何方法
2. 准备数据:距离计算所需要的数值,最好是结构化的数据格式
3. 分析数据:可以使用任何方法
4. 训练数据:此步骤不适用k-近邻算法
5. 测试数据:计算错误率
6. 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理
2. 实施kNN算法
对未知类别的属性的数据集中的每个点依次执行以下操作
算法流程:
1. 计算已知类别数据集中的点与当前点之间的距离
2. 按照距离递增次序排序
3. 选取与当前点距离最小的k个点
4. 确定前k个点所在类别的出现频率
5. 返回前k个点出现频率最高的类别作为当前点的预测分类
k-近邻算法
def classify0(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()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
classify0()
函数有4个输入参数:用于分类的输入向量是inX,输入的训练样本集为dataSet,标签向量为labels,k为选取距离最近的数量。
欧式距离公式:
示例:使用k-近邻算法改进约会网站的配对效果
示例:在约会网站上使用k-近邻算法 |
---|
(1)收集数据:提供文本文件 |
---|
(2)准备数据:使用Python解析文本文件 |
(3)分析数据:使用Matplotlib画二维扩散图 |
(4)训练算法:此步骤不适用于k-近邻算法 |
(5)测试算法:使用抽样数据作为测试样本 |
(6)使用算法:产生简单的命令行程序,然后输入一些特征数据以判断是否正确 |
1.准备数据:从文本文件中解析数据
数据保存在文本文件datingTestSet.txt中(CSDN可以下载相对应的数据集),每个样本数据占据一行,包含以下3中特征:
- [ ] 每年获得的飞行常客里程数
- [ ] 玩视频游戏所耗的时间百分比
- [ ] 每周消费的冰淇淋公升数
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式
file2matrix()
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines)
returnMat = zeros((numberOfLines, 3)) # 创建一个numberOfLines X 3的矩阵,初始化值为0
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip() # strip()删除序列开头和结尾匹配的字符,假如参数为空,则删除\n\r\t
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
2.分析数据:使用Matplotlib创建散点图
def test():
datingDataMat, datingLabels = kNN.file2matrix("d:/pythonfile/kNN/datingTestSet.txt")
fig = plt.figure()
ax = fig.add_subplot(111) # add_subplot增加子图,111表示创建一行一列的图,而ax为第一个 也可以表示为(1,1,1)
ax.scatter(datingDataMat[:, 0], datingDataMat[:, 1], 15.0 * array(datingLabels), 15.0 * array(datingLabels)) # 参数:x轴,y轴,点的大小,点的色彩
plt.show()
以第一类和第二类特征组合的散点图能清晰地标识了三个不同的样本分类区域
3.准备数据:归一化数值
根据欧拉公式可以容易发现,数字差值最大的属性对计算结果影响最大。
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,将取值范围处理为0到1或者-1到1之间。
autoNorm()
def autoNorm(dataSet):
minVals = dataSet.min(0) # min(0)中的参数0使得函数可以从列中取得最小值,返回矩阵
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet)) # 以dataset的数据结构置0
m = dataSet.shape[0] # 获取行数
normDataSet = dataSet - tile(minVals, (m, 1)) # tile把min值以(1Xm,nX1)的形式复制使得大小和dataSet一样
normDataSet = normDataSet / tile(ranges, (m, 1))
return normDataSet, ranges, minVals
4.测试算法:作为完整程序验证分类器
数据按需处理后,我们需要测试分类器的效果
datingClassTest()
def datingClassTest():
hoRatio = 0.10 # 测试数据比例
datingDataMat, datingLabels = file2matrix("d:/pythonfile/kNN/datingTestSet.txt")
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0.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))
数据测试后,错误率只有5%。在验证了分类器的效果之后,我们可以写一个程序进行约会预测
classifyPerson()
def classifyPerson():
resultList = ["not at all", "in small doses", "in large doses"]
percentTats = float(raw_input("percentage of time spend playing video games?"))
ffMiles = float(raw_input("frequent fliter miles earned per year?"))
iceCream = float(raw_input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix("d:/pythonfile/kNN/datingTestSet.txt")
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0.classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
print "You will probably like this person:", resultList[classifierResult - 1]
按指示输入相对应的特征后,分类器会按照所输入的特征进行分类。
示例:手写识别系统
1.准备数据:将图像转换为测试向量
img2vector
def img2vector(filename):
returnVect = 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
2.测试数据
handwritingClassTest()
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir("d:/pythonfile/kNN/trainingDigits")
m = len(trainingFileList)
trainingMat = 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('d:/pythonfile/kNN/trainingDigits/%s' % fileNameStr)
testFileList = listdir("d:/pythonfile/kNN/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("d:/pythonfile/kNN/testDigits/%s" % fileNameStr)
classifilerResult = classify0.classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print "the classifier came back with: %d, the real answer is: %d" % (classifilerResult, classNumStr)
if (classifilerResult != classNumStr):
errorCount +=1.0
print "\nthe total number of errors is: %d" % errorCount
print "\nthe total rate is %f" % (errorCount/float(mTest))
小结
k-近邻算法是分类数据最简单最有效的算法,以上两个例子讲述了如何使用k-近邻算法构造分类器。k-近邻算法是基于实例的学习,使用算法时我们必须有接近实际数据的训练样本数据。k-近邻算法必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时,
k-近邻算法的另外一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均实例样本和典型实例样本和实例样本的具有什么特征。