KNN算法--约会网站(机器学习实战例子)

本文详细介绍了KNN算法的工作原理,包括距离度量、近邻选择、决策规则,以及特征缩放和超参数选择的重要性。文章还展示了如何在实际应用中,如约会网站匹配中,使用KNN算法进行数据预处理和测试,以及如何通过Python实现关键步骤。
摘要由CSDN通过智能技术生成

KNN算法,即K最近邻(K-Nearest Neighbors)算法,是一种基于实例的学习或懒惰学习算法。其核心原理概述如下:

1. 距离度量:计算待分类点与训练集中所有点之间的距离,通常使用的距离度量包括欧氏距离、曼哈顿距离等。
2. 选择近邻:根据距离度量找出最近的K个邻居,K的选择对算法的结果有很大影响。K值较小时,模型可能会对数据的噪声和异常值更敏感;而K值较大时,模型可能会过于简化,失去对数据细节的捕捉能力。
3. 多数表决:在分类任务中,待分类点的类别由这K个近邻中出现最多的类别决定。在回归任务中,则通常是取这K个近邻的平均值或加权平均作为预测结果。
4. 决策规则:KNN算法不具有强推广性,因为它是基于记忆的,只是简单地根据训练数据中的最近邻进行决策,而不是通过学习得到一个泛化模型。
5. 特征缩放:由于KNN依赖于距离计算,因此特征的不同量级可能会影响结果。通常需要对特征进行缩放,以避免某些特征因量级过大而对距离计算产生不成比例的影响。
6. 超参数选择:KNN算法中的K值是一个重要的超参数,通常可以通过交叉验证等方法来选择最优的K值。

综上所述,KNN算法因其简单易懂而被广泛应用于各种机器学习问题中,尤其是在数据量不大或者特征空间维度较低的情况下,KNN算法往往能够取得不错的效果。尽管它在某些情况下可能不如复杂的模型那样强大,但KNN算法的直观性和易实现性使其在实际应用中仍然具有一定的价值。 
 

示例:在约会网站上使用k-近邻算法(步骤)
(1)收集数据:提供文本文件

  (2)准备数据:使用Python解析文本文件。

(3)分析数据:使用Matplotlib画二维扩散图。

  (4)训练算法:此步骤不适用于k-近邻算法。

(5)测试算法:使用海伦提供的部分数据作为测试样本。
测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类
与实际类别不同,则标记为一个错误。
  (6)使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否
为自己喜欢的类型。

1.1准备数据:从文本文件中解析数据
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet2.bxt中,
每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
每年获得的飞行常客里程数

玩视频游戏所耗时间百分比

每周消费的冰淇淋公升数


在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
将下面的代码增加到kNN.py中。

def file2matrix(filename):
    '''
    读取模块
    :param filename: 数据集文件名
    :return: 将特征数据和类别数据分开传出
    '''
    fr = open(filename)
    arrayOLines = fr.readlines()
    # print(arrayOLines)#读取filename中的内容,这里的数据集为txt文件 要获得所有内容:readlines()
    numberOfLines = len(arrayOLines)
    # print(numberOfLines)
    returnMat = zeros((numberOfLines,3))
    # print(returnMat)# 生成一个行为numberOfLines列为3的零矩阵
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        #将空格消掉
        listFromLine = line.split(' ')
        # print(listFromLine)#以空格为分隔标记,将字符串拆分成列表
        returnMat[index,:] = listFromLine[0:3]
        # print(returnMat)#前三个数据由returnMat存储
        classLabelVector.append((int(listFromLine[-1])))
        #最后一个数据由classLabelVector存储
        index += 1
    return returnMat, classLabelVector
 

从上面的代码可以看到,Python处理文本文件非常容易。首先我们需要知道文本文件包含多少行。打开文件,得到文件的行数①。然后创建以零填充的矩阵NumPy②(实际上,NumPy是一个二维数组,这里暂时不用考虑其用途)。为了简化处理,我们将该矩阵的另一维度设置为固定值3,你可以按照自己的实际需求增加相应的代码以适应变化的输入值。循环处理文件中的每行数据目,首先使用函数1ine.strip()截取掉所有的回车字符,然后使用tab字符\将上一步得到的整行数据分割成一个元素列表。接着,我们选取前3个元素,将它们存储到特征矩阵中。Python语言可以使用索引值-1表示列表中的最后一列元素,利用这种负索引,我们可以很方便地将列表的最后一列存储到向量classLabe1vector中。需要注意的是,我们必须明确地通知解释器,告诉它列表中存储的元素值为整型,否则Python语言会将这些元素当作字符串处理。以前我们必须自己处理这些变量值类型问题,现在这些细节问题完全可以交给NumPy函数库来处理。

使用函数fi1e2matrix读取文件数据,必须确保文件datingTestSet.txt存储在我们的工作目录中。此外在执行这个函数之前,我们重新加载了kNN.py模块,以确保更新的内容可以生效,否则Python将继续使用上次加载的kNN模块。

1.2分析数据:使用Matplotlib创建散点图

代码:

def show_file2matrix():
    Mat,Labels = file2matrix(('hailun.txt'))
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(Mat[:,0],Mat[:,1],15.0*array(Labels),15.0*array(Labels))
    #scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, hold=None, data=None, **kwargs)
    #前两位是输入的数据,第三位传入大小的参数,第四位传入颜色的参数
    plt.show()
 

scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, hold=None, data=None, **kwargs)
    #前两位是输入的数据,第三位传入大小的参数,第四位传入颜色的参数

以飞行里程数作为x轴,以看视频玩游戏时间占比作为y轴,将数据打印出来得:

1.3KNN算法
k-近邻算法对位置类别属性的数据集的每个点依次执行以下操作:

  1. 计算已知类别数据集中的点与当前点之间的距离;

  2. 按照距离递增次序排序;

  3. 选取与当前点距离最小的k个点;

  4. 确定前k个点所在的类别的出现频率;

  5. 返回前k个点出现频率最高的类别作为当前点的预测分类。

def classify0(inX, dataSet, labels, k):
    '''
    训练模块
    kNN分类算法实现,近朱者赤近墨者黑
    :param inX: 传入需要测试的列表
    :param dataSet: 特征集合
    :param labels: 类别集合
    :param k: kNN
    :return:
    '''
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    sgDiffMat = diffMat**2
    sgDistances = sgDiffMat.sum(axis=1)
    distances = sgDistances**0.5
    #计算距离
    sortedDistIndicies = distances.argsort()
    classCount={}
    for i in range(k):
        #选择距离最小的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]

KNN算法的优点主要包括简单易懂、无需参数估计、训练时间为零、适用于多分类问题等,具体如下:

1. 简单易懂:KNN算法的原理直观,易于理解和实现。它基于距离度量来对新的数据点进行分类,不需要复杂的数学推导。
2. 无需参数估计:与需要估计参数的模型不同,KNN是一种非参数化算法,这意味着它不需要对数据的分布做出任何假设。
3. 训练时间为零:KNN没有显式的训练过程,因为它只是存储训练样本,并在需要时使用这些样本来分类新的输入。因此,KNN的训练时间实际上是零。
4. 适用于多分类问题:KNN算法能够天然地处理多分类问题,对于稀有事件的分类也较为适合。

KNN算法的缺点则包括计算量大、对稀有类别预测准确率低、需要大量内存、预测速度慢以及可解释性差等,具体如下:

1. 计算量大:在特征数量很多的情况下,计算每个测试样本与所有训练样本之间的距离会变得非常耗时。
2. 对稀有类别预测准确率低:当样本不平衡时,稀有类别的预测准确率可能会较低。
3. 需要大量内存:为了快速查找最近邻,KNN可能需要使用KD树或球树等数据结构,这些结构的建立和维护需要大量的内存。
4. 预测速度慢:作为一种懒惰学习方法,KNN在预测时需要计算待分类点与所有训练样本的距离,这通常比像逻辑回归这样的算法慢。
5. 可解释性差:相比于决策树等模型,KNN模型的可解释性不强,不容易理解其内部的决策过程。

总的来说,尽管KNN算法在某些情况下可能不是最优选择,但它的简单性和适用性使其在许多实际问题中仍然是一个非常有用的工具。特别是在数据量不大,且特征空间比较简单的情况下,KNN算法可以提供快速且有效的解决方案。

1.4准备数据:归一化数值

在进行数据分析时,关于海伦约会的三个特征的平均值差距很大,但是这三个特征对决定类别都起着相同重要的作用,所以要对数据集合进行归一化。

在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:① newValue = (oldValue-min)/(max-min)
其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。我们需要在文件kNN.py中增加一个新函数autoNorm(),该函数可以自动将数字特征值转化为0到1的区间。
程序清单1-3提供了函数autoNorm()的代码。

def autoNorm(dataSet):
    '''
    归一化模块
    方程: newValue = (oldValue - min) / (max - min)
    :param dataSet: 需要归一化的数据集
    :return: 归一化后的数据集,最大值和最小值的差,最小值
    '''
    minVals = dataSet.min(0)
    # print(minVals)#得到最小的那行数据
    maxVals = dataSet.max(0)
    # print(maxVals)#得到最大的那行数据
    ranges = maxVals - minVals
    # print(ranges)#取两个最大最小行的差值
    normDataSet = zeros(dataSet.shape)#定义一个和dataSet一样大的零矩阵方便后面操作
    m = dataSet.shape[0]#m为dataSet的形状,shape[0]指列值
    normDataSet = dataSet - tile(minVals,(m,1))
    normDataSet = normDataSet/tile(ranges,(m,1))
    #numDataSet即为归一化之后的值
    return normDataSet, ranges, minVals

利用Matplotlib模块观察数据集

def show_autoNorm():
    '''
    秀出归一化之后的直角坐标图
    :return:
    '''
    Mat, Lables = file2matrix('hailun.txt')
    normMat, ranges, minVals = autoNorm(Mat)
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(normMat[:, 0], normMat[:, 1], 15.0 * array(Lables), 15.0 * array(Lables))
    plt.show()

以飞行里程数作为x轴,以看视频玩游戏时间占比作为y轴,将数据打印出来得: 

1.5测试算法:作为完整程序验证分类器
如果分类器的正确率满足要求,海伦就可以使用这个软件来处理约会网站提供的约会名单了。机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其金的10%数据去测试分类器,检测分类器的正确率。需要注意的是,10%的测试数据应该是随机选择的,由于海伦提供的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性。
前面我们已经提到可以使用错误率来检测分类器的性能。对于分类器来说,错误率就是分类器给出错误结果的次数除以测试数据的总数,完美分类器的错误率为0,而错误率为1.0的分类器不会给出任何正确的分类结果。代码里我们定义一个计数器变量,每次分类器错误地分类数据,计数器就加1,程序执行完成之后计数器的结果除以数据点总数即是错误率。
为了测试分类器效果,在kNN.py文件中创建函数datingClassTest,该函数是自包含的,
你可以在任何时候在Python运行环境中使用该函数测试


 def datingClassTest():
    '''
    测试模块
    :return:
    '''
    hoRatio = 0.10
    datingDataMat, datingLabels = file2matrix('hailun.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # normMat为归一化之后的值
    m = normMat.shape[0]
    # m为normMat的行数
    numTestVecs = int(m*hoRatio)
    # 0.10*行数,就是拿1/10的数据去测试
    errorCount = 0.0
    # 用于存储错误率
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        #将除去测试数据集的数据集作为训练集合
        print("The classifier cameback 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)))

函数datingClassTest如程序清单1-4所示, 它首先使用了file2matrix和autoNorm函数从文件中读取数据并将其转换为归一化特征值。接着计算测试向量的数量, 此步决定了normMat向量中哪些数据用于测试, 哪些数据用于分类器的训练样本;然后将这两部分数据输入到原始kNN分类器函数classify0。最后,函数计算错误率并输出结果。注意此处我们使用原始分类器,在Python命令提示符下重新加载kNN模块, 并输入kNN. datingClassTest(),  执行分类器测试程序, 我们将得到下面的输出结果:
 

1.6使用算法:构建完整可用系统

上面我们已经在数据上对分类器进行了测试,现在终于可以使用这个分类器为海伦来对人们分类。我们会给海伦一小段程序, 通过该程序海伦会在约会网站上找到某个人并输入他的信息。程序会给出她对对方喜欢程度的预测值

def Input_Text():
    '''
    应用模块
    :return:
    '''
    hoRatin = 0.10
    datingDataMat, datingLabels = file2matrix('hailun.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    #m为normMat的行数
 
    for i in range(10):
        distance = float(input('distance?'))
        play_time = float(input('play_time?'))
        ice_cream = float(input('ice-cream?'))
        aim = [distance,play_time,ice_cream]
        normDataSet = zeros(1)
        m = 1
        normDataSet = aim - tile(minVals, (m,1))
        normDataSet = normDataSet/tile(ranges,(m,1))
        # print(normDataSet)
        #将输入归一化
        Result = classify0(aim,datingDataMat,datingLabels,3)
        print(Result)

输入之后,需要将输入的数据归一化

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值