前言

K-近邻算法(k-Nearest Neighbor algorithm),又称为KNN算法,是数据挖掘技术中最简单的方法,属于机器学习实践中的入门,这篇文章主要是用于上机操作。
先介绍和解释步骤,分段给出代码;最后再给出源码,只需修改一下文件路径即可运行(和py文件放在同一目录下)
而具体的文件和代码,在如下链接里:机器学习-KNN算法的python实现 - 数据集和源码


推荐使用Jupyter Notebook ,当然用pycharm也可以。

一、 k-近邻算法(KNN)

1.算法介绍

存在一个样本的数据集合,也称作训练样本集,样本集中每个数据存在标签,输入没有标签的新数据以后,将新数据的每个特征与样本集中的数据特征进行比较,找到与新数据的距离最近的k个“邻居”,如果这k个实例多属于某个类别新数据就属于这个类别。
通常在分类任务中用于投票法,而在回归任务中用平均法,有时候还可以根据距离的远近进行加权,距离越近的权重越大。

简单地说:离X最近的k个点决定X为哪一类

与其他机器学习算法的不同之处在于,KNN是一种 “懒惰学习(lazing learing)” 算法,没有显式的训练过程,仅仅在训练阶段把学习样本保存下来,训练的时间开销为 0,直到测试时才对样本进行处理 (原来机器也会摆烂…)

2.举个例子——电影分类

使用KNN算法 分类一个新的电影是爱情片还是动作片(非真实次数)

打斗镜头和接吻镜头就是电影的特征;电影属于爱情片还是动作片就是标签 (还有爱情动作片是吧)
什么样的电影能有一百多个接吻镜头?
而度量距离是使用 欧氏距离(二范数)
在这里插入图片描述
如图为在二维平面的分类结果,把新的电影归于爱情片
在这里插入图片描述

3.步骤描述

k-近邻算法的步骤:

(1)计算已知类别数据集中点与当前点的距离
(2)按照距离递增次序排序
(3)选取与当前点距离最小的k个点
(4)选取当前k个点所在类别的出现频率
(5)返回前k个点出现频率最高的类别作为当前的预测类别

4.来了——代码实现

from numpy import *
import operator
#导入数据
def createData():
    group=array([1.0,1.1],[1.0,1.0],[0.0,0.0],[0.0,0.1])
    labels=['A','A','B','B']
    return group,labels
# KNN分类算法
def classif(inx,dataset,labels,k):
    datasetsize=dataset.shape[0]
    diffmat=tile(inx,(datasetsize,1))-dataset
    DiffMat = diffmat**2
    sqDistances = DiffMat.sum(axis=1)
    distances=sqDistances**0.5
    sortedDistIndicies = distances.argsort() 
    classcount={}
    for i in range(k):
        vote=labels[sortedDistIndicies[i]]
        classcount[vote]=classcount.get(vote,0)+1
    sortedclasscount =sorted(classcount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedclasscount[0][0],classcount
# 运行代码
group,labels=createData()
a=classif([0,0],group,labels,3)

运行结果
请添加图片描述


二、实战之约会网站配对效果判断

1.导入数据

收集约会数据,将这些数据存放在文本文件 datingTestSet2.txt 中,总共1000行,包含三种特征:
每年的飞行里程数,玩游戏视频的时间百分比,每周冰淇淋公升数以及三个标签1,2,3分别表示不喜欢,一般喜欢,很喜欢
(很奇怪,找对象和玩游戏时间有关系我理解,但和飞行多少、吃多少冰激凌有半毛钱关系呀?)
数据文件
但是这些特征数据想要分类,必须将数据的格式转化为可以接受的格式(例如数组),因此需要有个函数来进行转换

#用于导入数据的函数
def file2matrix(filename):
    fr = open(filename)
    array0Lines = fr.readlines()
    numberOfLines = len(array0Lines)
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in array0Lines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector, array0Lines
    
#获得数据集
datingDataMat, datingLabels, array0Lines = file2matrix('datingTestSet2.txt')

2.分析数据

可视化最直观,使用matplotlib绘制散点图

import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111) 
#参数111的意思是:将画布分割成1行1列,图像画在从左到右从上到下的第1块
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
#datingDataMat是上面返回的数组特征,这里1和2分别对应第二列特征和第三列特征
plt.xlabel('Percentage of Time Spent Playing Video Games')#坐标轴名称
plt.ylabel('Liters of Ice Cream Consumed Per Week')
plt.show()

得到结果,如图为“玩游戏视频的时间百分比”,“每周冰淇淋公升数”的分布,不同颜色代表喜好程度;
请添加图片描述

fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
ax2.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.xlabel('frequent fliters miles per year ')
plt.ylabel('Liters of Ice Cream Consumed Per Week')
plt.show()

得到结果,如图为“每年飞行公里数”,“每周冰淇淋公升数”的分布,不同颜色代表喜好程度;
请添加图片描述

3.数据归一化

分析图像就会发现,里程数和公斤数这个数据大小相差太多了,那么数据大的将严重影响其它两个特征,那么此时就需要让这三个比值同等重要,此时就需要用到归一化
使用公式:newvalue=(oldvalue-min)/(max-min)

#数据归一化函数,增加一个新的函数autoNorm自动转化到(0,1)区间
def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]# 行数
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide
    return normDataSet, ranges, minVals
# 得到归一化的样本集
normMat, ranges, minVals = autoNorm(datingDataMat)

得到的数据集normMat如图:
请添加图片描述

4. 测试算法→使用错误率来检测性能

def datingClassTest():
    hoRatio = 0.1    #测试集和训练集的比例,测试集和训练集的比例1:9![请添加图片描述](https://img-blog.csdnimg.cn/5effccb1c74248f3b85ab1e852cf7a4b.png)

    datingDataMat,datingLabels, array0Lines = file2matrix('datingTestSet2.txt')      
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0] # 行数,也是样本总数 m
    numTestVecs = int(m*hoRatio)# 取所有样本中的一部分当成测试集
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with: %s, the real answer is: %s" % (classifierResult, datingLabels[i]))
        if (classifierResult[0]!= datingLabels[i]): errorCount += 1.0
    print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
    print(errorCount)
    print(numTestVecs)
#运行代码
datingClassTest()

得到的部分结果:
在这里插入图片描述

5. 构建完整的系统

此时我们通过输入他人信息来判断海伦对对方的喜欢程度

# 新的样本判断喜欢程度
def classifyPerson():
    resultList = ['unlike', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent playing video games>"))
    ffMiles = float(input("frequent fliters miles per year?"))
    iceCream = float(input('liters of ice cream consumed per year?'))
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLabels,3)
    print(classifierResult)
    print("you will probably like this person:", resultList[classifierResult[0]-1])
#运行代码    
classifyPerson()

测试结果如下:
请添加图片描述

6.总结分析

  1. 分类时只设定了k=3,没有讨论其他的k值大小;是否选择其他的k值,会对准确率有所提高?
  2. 测试集和训练集的划分,只是简单的按比例取前10%的数据作为测试集,剩下的为训练集,可以进一步使用交叉验证等方法。

完整的代码:

# 准备数据
def file2matrix(filename):
    fr = open(filename)
    array0Lines = fr.readlines()
    numberOfLines = len(array0Lines)
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in array0Lines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector, array0Lines


# KNN分类算法
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]                         # 查看当前数据集有多少列,返回列数
    diffMat = tile(inX,(dataSetSize,1)) - dataSet          # inX 是待输入数组,(,)使用维度
    sqDiffMat = diffMat**2                                 # 计算两点间距离公式用二次方
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5                           # 所有平方和最后根号也就是0.5次方
    sortedDistIndicies = distances.argsort()               # 排序
    classCount={}
    for i in range(k):                                    # for循环,前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], classCount             # 最后返回最高频率元素
    
#数据归一化
def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet/tile(ranges, (m,1))   
    return normDataSet, ranges, minVals

#测试算法错误率
def datingClassTest():
    hoRatio = 0.1      
    datingDataMat,datingLabels, array0Lines = file2matrix('datingTestSet2.txt')      
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with: %s, the real answer is: %s" % (classifierResult, datingLabels[i]))
        if (classifierResult[0]!= datingLabels[i]): errorCount += 1.0
    print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
    print(errorCount)
    print(numTestVecs)
    
datingClassTest()

# 进行新的预测
def classifyPerson():
    resultList = ['unlike', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent playing video games>"))
    ffMiles = float(input("frequent fliters miles per year?"))
    iceCream = float(input('liters of ice cream consumed per year?'))
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLabels,3)
    print(classifierResult)
    print("you will probably like this person:", resultList[classifierResult[0]-1])
    
classifyPerson()

三、实战之手写数字识别

1.准备数据

实际图像存储在文件夹trainingDigitstestDigits中,我们使用trainingDigits去训练分类器,使用testDigits去进行测试分类效果。
在这里插入图片描述

我们需要将这个32*32的二进制图像转换为1 * 1024的向量,那么就需要一个函数去进行转化

#把图片变成向量
def img2vector(filename):
    returnVect = zeros((1,1024))   
    fr = open(filename)
    #读取文件的32行32列,将其放在returnVector向量中
    for i in range(32):          
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])    
    return returnVect
    
testVector = img2vector('testDigits/0_13.txt')

2.使用算法识别手写数字

如图为trainingDigits中的文件名称,开头第一个数字就为图片的标签
在这里插入图片描述
手写数字识别代码如下,导入训练集和测试集,并输出错误率

from os import listdir
def handwritingClassTest():        
    hwLabels = []
    trainingFileList = listdir('trainingDigits') # listdir列出给定目录的文件名
    m = len(trainingFileList)     # m 是文件个数              
    trainingMat = zeros((m,1024))      #创建m行1024列的训练集汇总矩阵,zeros代表矩阵的元素都是0        
    for i in range(m):                        
        fileNameStr = trainingFileList[i]  # 矩阵的每一行是一个图像的,对行进行操作,先把每行的文件名给到fileNameStr    
        fileStr = fileNameStr.split('.')[0]     
        classNumStr = int(fileStr.split('_')[0])   # 把.和_去掉
        hwLabels.append(classNumStr)    # append作用是将()内内容加到hwLabels数组中,也就是把标签加入
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)  #使用转换函数转换为1*1024,如此循环i行,将训练集trainingMat填满
    #下面是测试集,导入文件时也同样的操作
    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)
         # 使用之间的classify0分类算法,对测试集进行测试,和真实结果对比,可以算出错误率
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) 
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult[0], classNumStr))
        if (classifierResult[0] != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount/float(mTest)))   
#运行识别数字程序
handwritingClassTest()

得到的运行结果如下:(k=3)
在这里插入图片描述

3.改进

  • 在不同的k值下的识别错误率比较

k=2时

在这里插入图片描述
k=4时
在这里插入图片描述
k=5时
在这里插入图片描述
考虑加上不同k下的错误率统计图,完整的代码如下:

from os import listdir
#把图片变成向量
def img2vector(filename):
    returnVect = zeros((1,1024))   
    fr = open(filename)
    #读取文件的32行32列,将其放在returnVector向量中
    for i in range(32):          
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])    
    return returnVect
#识别数字,输入正整数k
def handwritingClassTest(k):        
    hwLabels = []
    trainingFileList = listdir('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('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, k)   
        '''print("the classifier came back with: %d, the real answer is: %d" % (classifierResult[0], classNumStr))'''
        if (classifierResult[0] != classNumStr): errorCount += 1.0
    print("\n K=%d ,the total number of errors is: %d" % (k,errorCount))
    print("\n K=%d ,the total error rate is: %f" % (k,(errorCount/float(mTest))))   
    return errorCount/float(mTest)

#绘制不同k下的错误率统计图
y = []
for i in range(1, 10):
    y.append(handwritingClassTest(i))
fig = plt.figure()
ax = fig.add_subplot(111)
x = [i for i in range(1, 10)]
plt.bar(x, y, align='center')
plt.xlabel('the value of K', )
plt.ylabel('Error rate')
plt.title('K graph')
plt.show()

结果大致如图所示:
在这里插入图片描述

  • 还可以增加因距离远近的加权比重,这里感兴趣的小伙伴可以自己尝试一下哦

升华主题

KNN算法并不算难,属于机器学习入门基础,现在我们在学习了这个算法后再次想一想,其核心思想能用于解决其他什么问题呢?

  • 著名商业哲学家、成功学创始人吉米·罗恩说过:“把你最常接触的五个人平均起来,就是你自己,而这也可以预测你的未来会如何。”

古语有云:物以类聚,人以群分。燕雀不可能与鸿鹄为伍,想要站在群山之巅的人,亦不会与甘于平庸之辈相处。
我们耳熟能详的孟母三迁故事就是体现了周围人对你成长的影响,人在交友的过程中不断学习,潜移默化地受到对方的影响,这种影响也会伴随着人的一生,正所谓:“近朱者赤,近墨者黑。”

用机器学习的话说,距离你最近的K个“邻居”,决定了你是什么样的人,距离你最“近”的那些朋友,能看出你最真实的生活状况。

  • 人有不同的气质,你是什么样的气质,就会吸引什么样的人。你若优秀,自然会吸引优秀的人,从一个人朋友的身上,往往也能找到这个人的影子。

你在受到周围人的影响同时,也会影响其他人;类似“环境影响生物,生物改造环境”、“实践决定认知,而认知对实践有反作用”,都是说明作用是相互的。
围棋盘上的每一颗棋子无论身处何处都会影响整盘棋的胜负,只不过随着距离的增大,影响力会快速衰减,但永远不会为零。
本身处于周围人影响的同时,我们也可以通过改变自身而影响他人。舍友喊你打游戏时,叫上他一起自习;朋友约你去网吧,劝他来图书馆。

  • 眼界的高低,决定你格局的大小。

当你把K取成1时,你的眼睛只看到手上的事物就停下了,心甘情愿接受周围的同化;你把脚步局限在屋子里,舒舒服服地躺在沙发上摆烂;你只关心当下,得过且过是一天,一言不合就摆烂…

但是如果你尝试把K取成100呢?你可以通过网络了解到时代当今热点,国家出台新政策,政府发布新规范,行业迎来新机遇,就业有了新方向…看似距离我们很远的事物其实都和我们生活息息相关;
十鸟在林,不如一鸟在手,眼界高不仅看到当下,更要看到未来;格局大不计较眼前的小小得失,而是关注整盘棋的胜负。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐